From 918418f56b8684fbfa44c63f3ab9a6f05783fbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 08:04:13 +0200 Subject: [PATCH 01/44] feat(settings): centralize the plugin settings Create the plugin setting schema Define the current plugin settings Remove refactored code --- common/constants.ts | 1051 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 974 insertions(+), 77 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index c6f627e2a1..b328735eba 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -100,28 +100,6 @@ export const WAZUH_SECURITY_PLUGINS = [ // App configuration export const WAZUH_CONFIGURATION_CACHE_TIME = 10000; // time in ms; -export const WAZUH_CONFIGURATION_SETTINGS_NEED_RESTART = [ - 'wazuh.monitoring.enabled', - 'wazuh.monitoring.frequency', - 'cron.statistics.interval', - 'logs.level', -]; -export const WAZUH_CONFIGURATION_SETTINGS_NEED_HEALTH_CHECK = [ - 'pattern', - 'wazuh.monitoring.replicas', - 'wazuh.monitoring.creation', - 'wazuh.monitoring.pattern', - 'alerts.sample.prefix', - 'cron.statistics.index.name', - 'cron.statistics.index.creation', - 'cron.statistics.index.shards', - 'cron.statistics.index.replicas', - 'wazuh.monitoring.shards', -]; -export const WAZUH_CONFIGURATION_SETTINGS_NEED_RELOAD = [ - 'hideManagerAlerts', - 'customization.logo.sidebar' -]; // Reserved ids for Users/Role mapping export const WAZUH_API_RESERVED_ID_LOWER_THAN = 100; @@ -176,61 +154,6 @@ export const WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH = path.join( // Queue export const WAZUH_QUEUE_CRON_FREQ = '*/15 * * * * *'; // Every 15 seconds -// Default App Config -export const WAZUH_DEFAULT_APP_CONFIG = { - pattern: WAZUH_ALERTS_PATTERN, - 'checks.pattern': true, - 'checks.template': true, - 'checks.api': true, - 'checks.setup': true, - 'checks.fields': true, - 'checks.metaFields': true, - 'checks.maxBuckets': true, - 'checks.timeFilter': true, - 'extensions.pci': true, - 'extensions.gdpr': true, - 'extensions.hipaa': true, - 'extensions.nist': true, - 'extensions.tsc': true, - 'extensions.audit': true, - 'extensions.oscap': false, - 'extensions.ciscat': false, - 'extensions.aws': false, - 'extensions.office': false, - 'extensions.github': false, - 'extensions.gcp': false, - 'extensions.virustotal': false, - 'extensions.osquery': false, - 'extensions.docker': false, - timeout: 20000, - 'ip.selector': true, - 'ip.ignore': [], - 'xpack.rbac.enabled': true, - 'wazuh.monitoring.enabled': WAZUH_MONITORING_DEFAULT_ENABLED, - 'wazuh.monitoring.frequency': WAZUH_MONITORING_DEFAULT_FREQUENCY, - 'wazuh.monitoring.shards': WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, - 'wazuh.monitoring.replicas': WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, - 'wazuh.monitoring.creation': WAZUH_MONITORING_DEFAULT_CREATION, - 'wazuh.monitoring.pattern': WAZUH_MONITORING_PATTERN, - 'cron.prefix': WAZUH_STATISTICS_DEFAULT_PREFIX, - 'cron.statistics.status': WAZUH_STATISTICS_DEFAULT_STATUS, - 'cron.statistics.apis': [], - 'cron.statistics.interval': WAZUH_STATISTICS_DEFAULT_CRON_FREQ, - 'cron.statistics.index.name': WAZUH_STATISTICS_DEFAULT_NAME, - 'cron.statistics.index.creation': WAZUH_STATISTICS_DEFAULT_CREATION, - 'cron.statistics.index.shards': WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, - 'cron.statistics.index.replicas': WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, - 'alerts.sample.prefix': WAZUH_SAMPLE_ALERT_PREFIX, - hideManagerAlerts: false, - 'logs.level': 'info', - 'enrollment.dns': '', - 'enrollment.password': '', - 'customization.logo.app': '', - 'customization.logo.sidebar': '', - 'customization.logo.healthcheck':'', - 'customization.logo.reports': '' -}; - // Wazuh errors export const WAZUH_ERROR_DAEMONS_NOT_READY = 'ERROR3099'; @@ -413,3 +336,977 @@ export const DOCUMENTATION_WEB_BASE_URL = "https://documentation.wazuh.com"; // Default Elasticsearch user name context export const ELASTIC_NAME = 'elastic'; + + +// Plugin settings +export enum SettingCategory{ + HEALTH_CHECK, + GENERAL, + EXTENSIONS, + MONITORING, + STATISTICS, + SECURITY, + CUSTOMIZATION, +}; + +type TpluginSettingOptionsChoices = { + choices: {text: string, value: any}[] +}; + +type TpluginSettingOptionsFile = { + file: { + type: 'image' + extensions?: string[] + recommended?: { + dimensions?: { + width: number, + height: number, + unit: string + } + } + store?: { + relativePathFileSystem: string + filename: string + resolveStaticURL: (filename: string) => string + } + } +}; + +type TpluginSettingOptionsNumber = { + number: { + min?: number + max?: number + } +}; + +type TpluginSettingOptionsEditor = { + editor: { + language: string + } +}; + +type TpluginSettingOptionsSwitch = { + switch: { + values: { + disabled: {label?: string, value: any}, + enabled: {label?: string, value: any}, + } + } +}; + + +export enum EpluginSettingType{ + text = 'text', + textarea = 'textarea', + switch = 'switch', + number = 'number', + editor = 'editor', + select = 'select', +}; + +export type TpluginSetting = { + title: string + description: string + category: SettingCategory + type: EpluginSettingType + default: any + defaultHidden?: any + configurableFile: boolean, + configurableUI: boolean + requireHealthCheck?: boolean + requireReload?: boolean + requireRestart?: boolean + options?: TpluginSettingOptionsChoices | TpluginSettingOptionsNumber | TpluginSettingOptionsEditor | TpluginSettingOptionsSwitch + transformUIInputValue?: (value: boolean | string) => boolean +}; + +export type TPluginSettingWithKey = TpluginSetting & { key: string }; + +type TpluginSettings = { + [key: string]: TpluginSetting +}; + +export const PLUGIN_SETTINGS_CATEGORIES = { + [SettingCategory.HEALTH_CHECK]: { + title: 'Health check', + description: "Define which checks will be executed by the App's HealthCheck. Allowed values are: true, false" + }, + [SettingCategory.GENERAL]: { + title: 'General', + description: "General settings." + }, + [SettingCategory.EXTENSIONS]: { + title: 'Extensions', + description: "Extensions." + }, + [SettingCategory.SECURITY]: { + title: 'Security', + description: "Security." + }, + [SettingCategory.MONITORING]: { + title: 'Task:Monitoring', + description: "Monitoring." + }, + [SettingCategory.STATISTICS]: { + title: 'Task:Statistics', + description: "Statistics." + }, + [SettingCategory.CUSTOMIZATION]: { + title: 'Customization', + description: "Customization." + } +}; + + +export const PLUGIN_SETTINGS: TpluginSettings = { + "alerts.sample.prefix": { + title: "Sample alerts prefix", + description: "Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + default: WAZUH_SAMPLE_ALERT_PREFIX, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + }, + "checks.api": { + title: "API connection", + description: "Enable or disable the API health check when opening the app.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.fields": { + title: "Known fields", + description: "Enable or disable the known fields health check when opening the app.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.maxBuckets": { + title: "Set max buckets to 200000", + description: "Change the default value of the plugin platform max buckets configuration.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + }, + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.metaFields": { + title: "Remove meta fields", + description: "Change the default value of the plugin platform metaField configuration.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.pattern": { + title: "Index pattern", + description: "Enable or disable the index pattern health check when opening the app.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.setup": { + title: "API version", + description: "Enable or disable the setup health check when opening the app.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.template": { + title: "Index template", + description: "Enable or disable the template health check when opening the app.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "checks.timeFilter": { + title: "Set time filter to 24h", + description: "Change the default value of the plugin platform timeFilter configuration.", + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "cron.prefix": { + title: "Cron prefix", + description: "Define the index prefix of predefined jobs.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + default: WAZUH_STATISTICS_DEFAULT_PREFIX, + configurableFile: true, + configurableUI: true, + }, + "cron.statistics.apis": { + title: "Includes APIs", + description: "Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.editor, + default: [], + configurableFile: true, + configurableUI: true, + options: { + editor: { + language: 'json' + } + }, + }, + "cron.statistics.index.creation": { + title: "Index creation", + description: "Define the interval in which a new index will be created.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.select, + options: { + choices: [ + { + text: "Hourly", + value: "h" + }, + { + text: "Daily", + value: "d" + }, + { + text: "Weekly", + value: "w" + }, + { + text: "Monthly", + value: "m" + } + ] + }, + default: WAZUH_STATISTICS_DEFAULT_CREATION, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + }, + "cron.statistics.index.name": { + title: "Index name", + description: "Define the name of the index in which the documents will be saved.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.text, + default: WAZUH_STATISTICS_DEFAULT_NAME, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + }, + "cron.statistics.index.replicas": { + title: "Index replicas", + description: "Define the number of replicas to use for the statistics indices.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.number, + default: WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + options: { + number: { + min: 0 + } + }, + }, + "cron.statistics.index.shards": { + title: "Index shards", + description: "Define the number of shards to use for the statistics indices.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.number, + default: WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + options: { + number: { + min: 1 + } + }, + }, + "cron.statistics.interval": { + title: "Interval", + description: "Define the frequency of task execution using cron schedule expressions.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.text, + default: WAZUH_STATISTICS_DEFAULT_CRON_FREQ, + configurableFile: true, + configurableUI: true, + requireRestart: true, + }, + "cron.statistics.status": { + title: "Status", + description: "Enable or disable the statistics tasks.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.switch, + default: WAZUH_STATISTICS_DEFAULT_STATUS, + configurableFile: true, + configurableUI: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "customization.logo.app": { + title: "Logo App", + description: `Customize the logo displayed in the plugin menu.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + default: "", + configurableFile: true, + configurableUI: true, + }, + "customization.logo.healthcheck": { + title: "Logo Health Check", + description: `Customize the logo displayed in the plugin health check.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + default: "", + configurableFile: true, + configurableUI: true, + }, + "customization.logo.reports": { + title: "Logo Reports", + description: `Customize the logo displayed in the PDF reports.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + default: "", + defaultHidden: REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, + configurableFile: true, + configurableUI: true, + }, + "customization.logo.sidebar": { + title: "Logo Sidebar", + description: `Customize the logo of the category that belongs the plugin.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + default: "", + configurableFile: true, + configurableUI: true, + requireReload: true, + }, + "disabled_roles": { + title: "Disables roles", + description: "Disabled the plugin visibility for users with the roles.", + category: SettingCategory.SECURITY, + type: EpluginSettingType.editor, + default: [], + configurableFile: true, + configurableUI: true, + options: { + editor: { + language: 'json' + } + }, + }, + "enrollment.dns": { + title: "Enrollment DNS", + description: "Specifies the Wazuh registration server, used for the agent enrollment.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + default: "", + configurableFile: true, + configurableUI: true, + }, + "enrollment.password": { + title: "Enrollment Password", + description: "Specifies the password used to authenticate during the agent enrollment.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + default: "", + configurableFile: true, + configurableUI: false, + }, + "extensions.audit": { + title: "System auditing", + description: "Enable or disable the Audit tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.aws": { + title: "Amazon AWS", + description: "Enable or disable the Amazon (AWS) tab on Overview.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.ciscat": { + title: "CIS-CAT", + description: "Enable or disable the CIS-CAT tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.docker": { + title: "Docker Listener", + description: "Enable or disable the Docker listener tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.gcp": { + title: "Google Cloud platform", + description: "Enable or disable the Google Cloud Platform tab on Overview.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.gdpr": { + title: "GDPR", + description: "Enable or disable the GDPR tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.hipaa": { + title: "Hipaa", + description: "Enable or disable the HIPAA tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.mitre": { + title: "MITRE ATT&CK", + description: "Enable or disable the MITRE tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.nist": { + title: "NIST", + description: "Enable or disable the NIST 800-53 tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.oscap": { + title: "OSCAP", + description: "Enable or disable the Open SCAP tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.osquery": { + title: "Osquery", + description: "Enable or disable the Osquery tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.pci": { + title: "PCI DSS", + description: "Enable or disable the PCI DSS tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.tsc": { + title: "TSC", + description: "Enable or disable the TSC tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "extensions.virustotal": { + title: "Virustotal", + description: "Enable or disable the VirusTotal tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "hideManagerAlerts": { + title: "Hide manager alerts", + description: "Hide the alerts of the manager in every dashboard.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.switch, + default: false, + configurableFile: true, + configurableUI: true, + requireReload: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "ip.ignore": { + title: "Index pattern ignore", + description: "Disable certain index pattern names from being available in index pattern selector.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.editor, + default: [], + configurableFile: true, + configurableUI: true, + options: { + editor: { + language: 'json' + } + }, + }, + "ip.selector": { + title: "IP selector", + description: "Define if the user is allowed to change the selected index pattern directly from the top menu bar.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.switch, + default: true, + configurableFile: true, + configurableUI: false, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "logs.level": { + title: "Log level", + description: "Logging level of the App.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.select, + options: { + choices: [ + { + text: "Info", + value: "info" + }, + { + text: "Debug", + value: "debug" + } + ] + }, + default: "info", + configurableFile: true, + configurableUI: true, + requireRestart: true, + }, + "pattern": { + title: "Index pattern", + description: "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + default: WAZUH_ALERTS_PATTERN, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + }, + "timeout": { + title: "Request timeout", + description: "Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.number, + default: 20000, + configurableFile: true, + configurableUI: true, + options: { + number: { + min: 1500 + } + }, + }, + "wazuh.monitoring.creation": { + title: "Index creation", + description: "Define the interval in which a new wazuh-monitoring index will be created.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.select, + options: { + choices: [ + { + text: "Hourly", + value: "h" + }, + { + text: "Daily", + value: "d" + }, + { + text: "Weekly", + value: "w" + }, + { + text: "Monthly", + value: "m" + } + ] + }, + default: WAZUH_MONITORING_DEFAULT_CREATION, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + }, + "wazuh.monitoring.enabled": { + title: "Status", + description: "Enable or disable the wazuh-monitoring index creation and/or visualization.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.switch, + default: WAZUH_MONITORING_DEFAULT_ENABLED, + configurableFile: true, + configurableUI: true, + requireRestart: true, + options: { + switch: { + values: { + disabled: {label: 'false', value: false}, + enabled: {label: 'true', value: true}, + } + } + }, + transformUIInputValue: function(value: boolean | string): boolean{ + return Boolean(value); + }, + }, + "wazuh.monitoring.frequency": { + title: "Frequency", + description: "Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.number, + default: WAZUH_MONITORING_DEFAULT_FREQUENCY, + configurableFile: true, + configurableUI: true, + requireRestart: true, + options: { + number: { + min: 60 + } + }, + }, + "wazuh.monitoring.pattern": { + title: "Index pattern", + description: "Default index pattern to use for Wazuh monitoring.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.text, + default: WAZUH_MONITORING_PATTERN, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + }, + "wazuh.monitoring.replicas": { + title: "Index replicas", + description: "Define the number of replicas to use for the wazuh-monitoring-* indices.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.text, + default: WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + options: { + number: { + min: 0 + } + }, + }, + "wazuh.monitoring.shards": { + title: "Index shards", + description: "Define the number of shards to use for the wazuh-monitoring-* indices.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.number, + default: WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, + configurableFile: true, + configurableUI: true, + requireHealthCheck: true, + options: { + number: { + min: 1 + } + }, + } +}; From de48a49fd44ddd0da424178d3508f154798a7583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 08:35:59 +0200 Subject: [PATCH 02/44] feat(settings): add setting services and replaced the references to constants --- common/services/settings.ts | 33 +++++++++++++++++++ .../subrequirements/subrequirements.tsx | 6 ++-- .../components/techniques/techniques.tsx | 6 ++-- .../wz-agent-selector/wz-agent-selector.js | 4 +-- public/controllers/agent/agents.js | 5 +-- .../overview/components/alerts-stats.js | 4 +-- .../overview-actions/overview-actions.js | 4 +-- public/controllers/overview/overview.js | 5 +-- public/kibana-integrations/kibana-discover.js | 4 +-- public/redux/reducers/appConfigReducers.ts | 4 +-- public/services/resolves/get-config.js | 4 +-- server/controllers/wazuh-elastic.ts | 7 ++-- server/lib/reporting/audit-request.ts | 8 ++--- server/lib/reporting/extended-information.ts | 4 +-- server/lib/reporting/gdpr-request.ts | 6 ++-- server/lib/reporting/overview-request.ts | 4 +-- server/lib/reporting/pci-request.ts | 6 ++-- server/lib/reporting/rootcheck-request.ts | 8 ++--- server/lib/reporting/summary-table.ts | 4 +-- server/lib/reporting/syscheck-request.ts | 10 +++--- server/lib/reporting/tsc-request.ts | 6 ++-- server/lib/reporting/vulnerability-request.ts | 12 +++---- .../start/cron-scheduler/scheduler-handler.ts | 7 ++-- server/start/initialize/index.ts | 7 ++-- server/start/monitoring/index.ts | 23 +++++-------- .../apps/overview/_integrity_monitoring.ts | 4 +-- .../apps/overview/_security_events.ts | 4 +-- test/server/wazuh-elastic.js | 16 ++++----- 28 files changed, 123 insertions(+), 92 deletions(-) create mode 100644 common/services/settings.ts diff --git a/common/services/settings.ts b/common/services/settings.ts new file mode 100644 index 0000000000..5f0c532922 --- /dev/null +++ b/common/services/settings.ts @@ -0,0 +1,33 @@ +import { EpluginSettingType, PLUGIN_SETTINGS, TpluginSetting } from '../constants'; + +/** + * Get the default value of the plugin setting + * @param setting setting key + * @returns setting default value + */ +export function getSettingDefaultValue(setting: string) { + return typeof PLUGIN_SETTINGS[setting].defaultHidden !== 'undefined' + ? PLUGIN_SETTINGS[setting].defaultHidden + : PLUGIN_SETTINGS[setting].default; +}; + +export function getSettingsDefault() { + return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ({ + ...accum, + [pluginSettingID]: pluginSettingConfiguration.default + }), {}); +}; + +export function getSettingsByCategories() { + return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ({ + ...accum, + [pluginSettingConfiguration.category]: [...(accum[pluginSettingConfiguration.category] || []), { ...pluginSettingConfiguration, key: pluginSettingID }] + }), {}); +}; + +export function getSettingsDefaultList() { + return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ([ + ...accum, + { ...pluginSettingConfiguration, key: pluginSettingID } + ]), []); +}; diff --git a/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx b/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx index 43090a7510..b75770a669 100644 --- a/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx +++ b/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx @@ -24,15 +24,13 @@ import { EuiPopover, EuiText, EuiIcon, - EuiOverlayMask, - EuiOutsideClickDetector, EuiLoadingSpinner, } from '@elastic/eui'; import { AppNavigate } from '../../../../../react-services/app-navigate'; import { AppState } from '../../../../../react-services/app-state'; import { RequirementFlyout } from '../requirement-flyout/requirement-flyout'; -import { WAZUH_ALERTS_PATTERN } from '../../../../../../common/constants'; import { getDataPlugin } from '../../../../../kibana-services'; +import { getSettingDefaultValue } from '../../../../../../common/services/settings'; export class ComplianceSubrequirements extends Component { _isMount = false; @@ -71,7 +69,7 @@ export class ComplianceSubrequirements extends Component { params: { query: filter.value }, type: 'phrase', negate: filter.negate || false, - index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN, + index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), }, query: { match_phrase: matchPhrase }, $state: { store: 'appState' }, diff --git a/public/components/overview/mitre/components/techniques/techniques.tsx b/public/components/overview/mitre/components/techniques/techniques.tsx index 8234de0b73..50c4f10b28 100644 --- a/public/components/overview/mitre/components/techniques/techniques.tsx +++ b/public/components/overview/mitre/components/techniques/techniques.tsx @@ -9,7 +9,7 @@ * * Find more information about this on the LICENSE file. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import { EuiFacetButton, EuiFlexGroup, @@ -31,13 +31,13 @@ import { getElasticAlerts, IFilterParams } from '../../lib'; import { ITactic } from '../../'; import { withWindowSize } from '../../../../../components/common/hocs/withWindowSize'; import { WzRequest } from '../../../../../react-services/wz-request'; -import { WAZUH_ALERTS_PATTERN } from '../../../../../../common/constants'; import { AppState } from '../../../../../react-services/app-state'; import { WzFieldSearchDelay } from '../../../../common/search'; import { getDataPlugin, getToasts } from '../../../../../kibana-services'; import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../../../react-services/common-services'; +import { getSettingDefaultValue } from '../../../../../../common/services/settings'; const MITRE_ATTACK = 'mitre-attack'; @@ -430,7 +430,7 @@ export const Techniques = withWindowSize( params: { query: filter.value }, type: 'phrase', negate: filter.negate || false, - index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN, + index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), }, query: { match_phrase: matchPhrase }, $state: { store: 'appState' }, diff --git a/public/components/wz-agent-selector/wz-agent-selector.js b/public/components/wz-agent-selector/wz-agent-selector.js index bf00fc0b0b..d95b595073 100644 --- a/public/components/wz-agent-selector/wz-agent-selector.js +++ b/public/components/wz-agent-selector/wz-agent-selector.js @@ -23,7 +23,7 @@ import { connect } from 'react-redux'; import { showExploreAgentModalGlobal } from '../../redux/actions/appStateActions'; import store from '../../redux/store'; import { AgentSelectionTable } from '../../controllers/overview/components/overview-actions/agents-selection-table'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; import { AppState } from '../../react-services/app-state'; import { getAngularModule, getDataPlugin } from '../../kibana-services'; @@ -70,7 +70,7 @@ class WzAgentSelector extends Component { "negate": false, "params": { "query": agentIdList[0] }, "type": "phrase", - "index": AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN + "index": AppState.getCurrentPattern() || getSettingDefaultValue('pattern') }, "query": { "match": { diff --git a/public/controllers/agent/agents.js b/public/controllers/agent/agents.js index f557807b2c..98deeea151 100644 --- a/public/controllers/agent/agents.js +++ b/public/controllers/agent/agents.js @@ -28,12 +28,13 @@ import { ErrorHandler } from '../../react-services/error-handler'; import { GroupHandler } from '../../react-services/group-handler'; import store from '../../redux/store'; import { updateGlobalBreadcrumb } from '../../redux/actions/globalBreadcrumbActions'; -import { API_NAME_AGENT_STATUS, WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { API_NAME_AGENT_STATUS } from '../../../common/constants'; import { getDataPlugin } from '../../kibana-services'; import { hasAgentSupportModule } from '../../react-services/wz-agents'; import { UI_LOGGER_LEVELS } from '../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../react-services/common-services'; +import { getSettingDefaultValue } from '../../../common/services/settings'; export class AgentsController { /** @@ -618,7 +619,7 @@ export class AgentsController { */ addMitrefilter(id) { const filter = `{"meta":{"index": ${ - AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN + AppState.getCurrentPattern() || getSettingDefaultValue('pattern') }},"query":{"match":{"rule.mitre.id":{"query":"${id}","type":"phrase"}}}}`; this.$rootScope.$emit('addNewKibanaFilter', { filter: JSON.parse(filter), diff --git a/public/controllers/overview/components/alerts-stats.js b/public/controllers/overview/components/alerts-stats.js index 15ab7a9463..dca476c482 100644 --- a/public/controllers/overview/components/alerts-stats.js +++ b/public/controllers/overview/components/alerts-stats.js @@ -17,9 +17,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import { connect } from 'react-redux'; import { buildPhrasesFilter, buildRangeFilter } from '../../../../../../src/plugins/data/common'; import { getIndexPattern } from '../../../../public/components/overview/mitre/lib'; -import { WAZUH_ALERTS_PATTERN } from '../../../../common/constants'; import { AppState } from '../../../react-services/app-state'; import { getDataPlugin } from '../../../kibana-services'; +import { getSettingDefaultValue } from '../../../../common/services/settings'; class AlertsStats extends Component { @@ -90,7 +90,7 @@ class AlertsStats extends Component { "params": { "query": filter.value }, "type": "phrase", "negate": filter.negate || false, - "index": AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN + "index": AppState.getCurrentPattern() || getSettingDefaultValue('pattern') }, "query": { "match_phrase": matchPhrase }, "$state": { "store": "appState" } diff --git a/public/controllers/overview/components/overview-actions/overview-actions.js b/public/controllers/overview/components/overview-actions/overview-actions.js index 4bd84ffb22..6d7d04f1e6 100644 --- a/public/controllers/overview/components/overview-actions/overview-actions.js +++ b/public/controllers/overview/components/overview-actions/overview-actions.js @@ -27,9 +27,9 @@ import { import { WzButton } from '../../../../components/common/buttons'; import './agents-selector.scss'; import { AgentSelectionTable } from './agents-selection-table'; -import { WAZUH_ALERTS_PATTERN } from '../../../../../common/constants'; import { AppState } from '../../../../react-services/app-state'; import { getDataPlugin } from '../../../../kibana-services'; +import { getSettingDefaultValue } from '../../../../../common/services/settings'; class OverviewActions extends Component { constructor(props) { @@ -110,7 +110,7 @@ class OverviewActions extends Component { negate: false, params: { query: agentIdList[0] }, type: 'phrase', - index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN, + index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), }, query: { match: { diff --git a/public/controllers/overview/overview.js b/public/controllers/overview/overview.js index da7d97f511..5404d50e83 100644 --- a/public/controllers/overview/overview.js +++ b/public/controllers/overview/overview.js @@ -22,10 +22,11 @@ import { updateCurrentTab, updateCurrentAgentData } from '../../redux/actions/ap import { VisFactoryHandler } from '../../react-services/vis-factory-handler'; import { RawVisualizations } from '../../factories/raw-visualizations'; import store from '../../redux/store'; -import { UI_LOGGER_LEVELS, WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { UI_LOGGER_LEVELS } from '../../../common/constants'; import { getDataPlugin } from '../../kibana-services'; import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../react-services/common-services'; +import { getSettingDefaultValue } from '../../../common/services/settings'; export class OverviewController { /** @@ -358,7 +359,7 @@ export class OverviewController { * @param {*} id */ addMitrefilter(id) { - const filter = `{"meta":{ "index": ${AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN}},"query":{"match":{"rule.mitre.id":{"query":"${id}","type":"phrase"}}}}`; + const filter = `{"meta":{ "index": ${AppState.getCurrentPattern() || getSettingDefaultValue('pattern')}},"query":{"match":{"rule.mitre.id":{"query":"${id}","type":"phrase"}}}}`; this.$rootScope.$emit('addNewKibanaFilter', { filter: JSON.parse(filter) }); } diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js index fbb3e472ea..247901b839 100644 --- a/public/kibana-integrations/kibana-discover.js +++ b/public/kibana-integrations/kibana-discover.js @@ -84,7 +84,6 @@ import { UI_SETTINGS, } from '../../../../src/plugins/data/public'; import { addFatalError } from '../../../../src/plugins/kibana_legacy/public'; -import { WAZUH_ALERTS_PATTERN } from '../../common/constants'; import { DEFAULT_COLUMNS_SETTING, SAMPLE_SIZE_SETTING, @@ -97,6 +96,7 @@ import { createFixedScroll } from './discover/application/angular/directives/fix import './discover/application/index.scss'; import { getFilterWithAuthorizedAgents } from '../react-services/filter-authorization-agents'; +import { getSettingDefaultValue } from '../../common/services/settings'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -547,7 +547,7 @@ function discoverController( negate: true, params: { query: '000' }, type: 'phrase', - index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN + index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern') }, query: { match_phrase: { 'agent.id': '000' } }, $state: { store: 'appState' } diff --git a/public/redux/reducers/appConfigReducers.ts b/public/redux/reducers/appConfigReducers.ts index 4157abf4ef..5f3d43fd92 100644 --- a/public/redux/reducers/appConfigReducers.ts +++ b/public/redux/reducers/appConfigReducers.ts @@ -11,14 +11,14 @@ */ import { Reducer } from 'redux'; -import { WAZUH_DEFAULT_APP_CONFIG } from '../../../common/constants'; +import { getSettingsDefault } from '../../../common/services/settings'; import { AppConfigState, ResolverAction } from '../types'; const initialState: AppConfigState = { isLoading: false, isReady: false, hasError: false, - data: WAZUH_DEFAULT_APP_CONFIG, + data: getSettingsDefault(), }; const appConfigReducer: Reducer = ( diff --git a/public/services/resolves/get-config.js b/public/services/resolves/get-config.js index ba4aa543c1..63f055e951 100644 --- a/public/services/resolves/get-config.js +++ b/public/services/resolves/get-config.js @@ -10,10 +10,10 @@ * Find more information about this on the LICENSE file. */ -import { WAZUH_DEFAULT_APP_CONFIG } from '../../../common/constants'; +import { getSettingsDefault } from '../../../common/services/settings'; export async function getWzConfig($q, genericReq, wazuhConfig) { - const defaultConfig = { ...WAZUH_DEFAULT_APP_CONFIG }; + const defaultConfig = getSettingsDefault(); try { const config = await genericReq.request('GET', '/utils/configuration', {}); diff --git a/server/controllers/wazuh-elastic.ts b/server/controllers/wazuh-elastic.ts index 4ef33c063a..b3b4cb6332 100644 --- a/server/controllers/wazuh-elastic.ts +++ b/server/controllers/wazuh-elastic.ts @@ -19,12 +19,13 @@ import { } from '../integration-files/visualizations'; import { generateAlerts } from '../lib/generate-alerts/generate-alerts-script'; -import { WAZUH_MONITORING_PATTERN, WAZUH_SAMPLE_ALERT_PREFIX, WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_SAMPLE_ALERTS_INDEX_SHARDS, WAZUH_SAMPLE_ALERTS_INDEX_REPLICAS } from '../../common/constants'; +import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_SAMPLE_ALERTS_INDEX_SHARDS, WAZUH_SAMPLE_ALERTS_INDEX_REPLICAS } from '../../common/constants'; import jwtDecode from 'jwt-decode'; import { ManageHosts } from '../lib/manage-hosts'; import { KibanaRequest, RequestHandlerContext, KibanaResponseFactory, SavedObject, SavedObjectsFindResponse } from 'src/core/server'; import { getCookieValueByName } from '../lib/cookie'; import { WAZUH_SAMPLE_ALERTS_CATEGORIES_TYPE_ALERTS, WAZUH_SAMPLE_ALERTS_DEFAULT_NUMBER_ALERTS } from '../../common/constants' +import { getSettingDefaultValue } from '../../common/services/settings'; export class WazuhElasticCtrl { wzSampleAlertsIndexPrefix: string @@ -47,7 +48,7 @@ export class WazuhElasticCtrl { */ getSampleAlertPrefix(): string { const config = getConfiguration(); - return config['alerts.sample.prefix'] || WAZUH_SAMPLE_ALERT_PREFIX; + return config['alerts.sample.prefix'] || getSettingDefaultValue('alerts.sample.prefix'); } /** @@ -345,7 +346,7 @@ export class WazuhElasticCtrl { try { const config = getConfiguration(); let monitoringPattern = - (config || {})['wazuh.monitoring.pattern'] || WAZUH_MONITORING_PATTERN; + (config || {})['wazuh.monitoring.pattern'] || getSettingDefaultValue('wazuh.monitoring.pattern'); log( 'wazuh-elastic:buildVisualizationsRaw', `Building ${app_objects.length} visualizations`, diff --git a/server/lib/reporting/audit-request.ts b/server/lib/reporting/audit-request.ts index cb67d0b192..6de588968e 100644 --- a/server/lib/reporting/audit-request.ts +++ b/server/lib/reporting/audit-request.ts @@ -11,7 +11,7 @@ */ import { Base } from './base-query'; import AuditMap from './audit-map'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** * Returns top 3 agents that execute sudo commands without success @@ -26,7 +26,7 @@ export const getTop3AgentsSudoNonSuccessful = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -93,7 +93,7 @@ export const getTop3AgentsFailedSyscalls = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -172,7 +172,7 @@ export const getTopFailedSyscalls = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; diff --git a/server/lib/reporting/extended-information.ts b/server/lib/reporting/extended-information.ts index 6bec747cc7..a4c27ec9a1 100644 --- a/server/lib/reporting/extended-information.ts +++ b/server/lib/reporting/extended-information.ts @@ -12,9 +12,9 @@ import * as SyscheckRequest from './syscheck-request'; import PCI from '../../integration-files/pci-requirements-pdfmake'; import GDPR from '../../integration-files/gdpr-requirements-pdfmake'; import TSC from '../../integration-files/tsc-requirements-pdfmake'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; import { ReportPrinter } from './printer'; import moment from 'moment'; +import { getSettingDefaultValue } from '../../../common/services/settings'; @@ -131,7 +131,7 @@ export async function extendedInformation( from, to, filters, - pattern = WAZUH_ALERTS_PATTERN, + pattern = getSettingDefaultValue('pattern'), agent = null ) { try { diff --git a/server/lib/reporting/gdpr-request.ts b/server/lib/reporting/gdpr-request.ts index c6cc5f97cb..2368984b0f 100644 --- a/server/lib/reporting/gdpr-request.ts +++ b/server/lib/reporting/gdpr-request.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** * Returns top 5 GDPR requirements @@ -25,7 +25,7 @@ export const topGDPRRequirements = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { if (filters.includes('rule.gdpr: exists')) { const [head, tail] = filters.split('AND rule.gdpr: exists'); @@ -82,7 +82,7 @@ export const getRulesByRequirement= async ( lte, filters, requirement, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { if (filters.includes('rule.gdpr: exists')) { const [head, tail] = filters.split('AND rule.gdpr: exists'); diff --git a/server/lib/reporting/overview-request.ts b/server/lib/reporting/overview-request.ts index 748ceffd3b..0a75268e70 100644 --- a/server/lib/reporting/overview-request.ts +++ b/server/lib/reporting/overview-request.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** * Returns top 3 agents with level 15 alerts @@ -20,7 +20,7 @@ import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability * @returns {Array} E.g:['000','130','300'] */ -export const topLevel15 = async (context, gte, lte, filters, pattern = WAZUH_ALERTS_PATTERN) => { +export const topLevel15 = async (context, gte, lte, filters, pattern = getSettingDefaultValue('pattern')) => { try { const base = {}; diff --git a/server/lib/reporting/pci-request.ts b/server/lib/reporting/pci-request.ts index e579532d89..a4ac21e1c4 100644 --- a/server/lib/reporting/pci-request.ts +++ b/server/lib/reporting/pci-request.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** * Returns top 5 PCI DSS requirements @@ -25,7 +25,7 @@ export const topPCIRequirements = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { if (filters.includes('rule.pci_dss: exists')) { filters = filters.replace('AND rule.pci_dss: exists', ''); @@ -96,7 +96,7 @@ export const getRulesByRequirement = async ( lte, filters, requirement, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { if (filters.includes('rule.pci_dss: exists')) { filters = filters.replace('AND rule.pci_dss: exists', ''); diff --git a/server/lib/reporting/rootcheck-request.ts b/server/lib/reporting/rootcheck-request.ts index c7d4cdd510..ba41e694df 100644 --- a/server/lib/reporting/rootcheck-request.ts +++ b/server/lib/reporting/rootcheck-request.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** * Returns top 5 rootkits found along all agents @@ -25,7 +25,7 @@ export const top5RootkitsDetected = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN, + pattern = getSettingDefaultValue('pattern'), size = 5 ) => { try { @@ -80,7 +80,7 @@ export const agentsWithHiddenPids = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -129,7 +129,7 @@ export const agentsWithHiddenPorts = async( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; diff --git a/server/lib/reporting/summary-table.ts b/server/lib/reporting/summary-table.ts index fb60099eb6..f2e1ddb766 100644 --- a/server/lib/reporting/summary-table.ts +++ b/server/lib/reporting/summary-table.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; interface SummarySetup { title: string; @@ -24,7 +24,7 @@ export default class SummaryTable { lte, filters, summarySetup: SummarySetup, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) { this._context = context; diff --git a/server/lib/reporting/syscheck-request.ts b/server/lib/reporting/syscheck-request.ts index 9016a93ee6..3edc293cb0 100644 --- a/server/lib/reporting/syscheck-request.ts +++ b/server/lib/reporting/syscheck-request.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** @@ -26,7 +26,7 @@ export const top3agents = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -78,7 +78,7 @@ export const top3Rules = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -137,7 +137,7 @@ export const lastTenDeletedFiles = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -190,7 +190,7 @@ export const lastTenModifiedFiles = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; diff --git a/server/lib/reporting/tsc-request.ts b/server/lib/reporting/tsc-request.ts index 753258a38e..4601142f67 100644 --- a/server/lib/reporting/tsc-request.ts +++ b/server/lib/reporting/tsc-request.ts @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; /** * Returns top 5 TSC requirements @@ -25,7 +25,7 @@ export const topTSCRequirements = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { if (filters.includes('rule.tsc: exists')) { filters = filters.replace('AND rule.tsc: exists', ''); @@ -96,7 +96,7 @@ export const getRulesByRequirement = async ( lte, filters, requirement, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { if (filters.includes('rule.tsc: exists')) { filters = filters.replace('AND rule.tsc: exists', ''); diff --git a/server/lib/reporting/vulnerability-request.ts b/server/lib/reporting/vulnerability-request.ts index c5e1c62d4b..bd3596a1bb 100644 --- a/server/lib/reporting/vulnerability-request.ts +++ b/server/lib/reporting/vulnerability-request.ts @@ -9,8 +9,8 @@ * * Find more information about this on the LICENSE file. */ +import { getSettingDefaultValue } from '../../../common/services/settings'; import { Base } from './base-query'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; /** * Returns top 3 agents for specific severity @@ -27,7 +27,7 @@ export const topAgentCount = async ( lte, severity, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -79,7 +79,7 @@ export const topCVECount = async ( gte, lte, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -125,7 +125,7 @@ export const uniqueSeverityCount = async ( lte, severity, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -170,7 +170,7 @@ export const topPackages = async ( lte, severity, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; @@ -219,7 +219,7 @@ export const topPackagesWithCVE = async( lte, severity, filters, - pattern = WAZUH_ALERTS_PATTERN + pattern = getSettingDefaultValue('pattern') ) => { try { const base = {}; diff --git a/server/start/cron-scheduler/scheduler-handler.ts b/server/start/cron-scheduler/scheduler-handler.ts index 25515fb296..242e8d80e3 100644 --- a/server/start/cron-scheduler/scheduler-handler.ts +++ b/server/start/cron-scheduler/scheduler-handler.ts @@ -3,9 +3,10 @@ import { configuredJobs } from './configured-jobs'; import { log } from '../../lib/logger'; import { getConfiguration } from '../../lib/get-configuration'; import cron from 'node-cron'; -import { WAZUH_STATISTICS_DEFAULT_PREFIX, WAZUH_STATISTICS_DEFAULT_NAME, WAZUH_STATISTICS_TEMPLATE_NAME } from '../../../common/constants'; +import { WAZUH_STATISTICS_TEMPLATE_NAME } from '../../../common/constants'; import { statisticsTemplate } from '../../integration-files/statistics-template'; import { delayAsPromise } from '../../../common/utils'; +import { getSettingDefaultValue } from '../../../common/services/settings'; const blueWazuh = '\u001b[34mwazuh\u001b[39m'; const schedulerErrorLogColors = [blueWazuh, 'scheduler', 'error']; @@ -67,8 +68,8 @@ const checkTemplate = async function (context) { ); const appConfig = await getConfiguration(); - const prefixTemplateName = appConfig['cron.prefix'] || WAZUH_STATISTICS_DEFAULT_PREFIX; - const statisticsIndicesTemplateName = appConfig['cron.statistics.index.name'] || WAZUH_STATISTICS_DEFAULT_NAME; + const prefixTemplateName = appConfig['cron.prefix'] || getSettingDefaultValue('cron.prefix'); + const statisticsIndicesTemplateName = appConfig['cron.statistics.index.name'] || getSettingDefaultValue('cron.statistics.index.name'); const pattern = `${prefixTemplateName}-${statisticsIndicesTemplateName}-*`; try { diff --git a/server/start/initialize/index.ts b/server/start/initialize/index.ts index ab33b4f5d1..5da7bf1f68 100644 --- a/server/start/initialize/index.ts +++ b/server/start/initialize/index.ts @@ -15,9 +15,10 @@ import { pluginPlatformTemplate } from '../../integration-files/kibana-template' import { getConfiguration } from '../../lib/get-configuration'; import { totalmem } from 'os'; import fs from 'fs'; -import { WAZUH_ALERTS_PATTERN, WAZUH_DATA_CONFIG_REGISTRY_PATH, WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME, WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH, PLUGIN_PLATFORM_NAME, PLUGIN_PLATFORM_INSTALLATION_USER_GROUP, PLUGIN_PLATFORM_INSTALLATION_USER, WAZUH_DEFAULT_APP_CONFIG, PLUGIN_APP_NAME } from '../../../common/constants'; +import { WAZUH_DATA_CONFIG_REGISTRY_PATH, WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME, WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH, PLUGIN_PLATFORM_NAME, PLUGIN_PLATFORM_INSTALLATION_USER_GROUP, PLUGIN_PLATFORM_INSTALLATION_USER, WAZUH_DEFAULT_APP_CONFIG, PLUGIN_APP_NAME } from '../../../common/constants'; import { createDataDirectoryIfNotExists } from '../../lib/filesystem'; import _ from 'lodash'; +import { getSettingDefaultValue, getSettingsDefault } from '../../../common/services/settings'; export function jobInitializeRun(context) { @@ -34,7 +35,7 @@ export function jobInitializeRun(context) { pattern = configurationFile && typeof configurationFile.pattern !== 'undefined' ? configurationFile.pattern - : WAZUH_ALERTS_PATTERN; + : getSettingDefaultValue('pattern'); } catch (error) { log('initialize', error.message || error); context.wazuh.logger.error( @@ -138,7 +139,7 @@ export function jobInitializeRun(context) { // Rebuild the registry file `wazuh-registry.json` // Get the supported extensions for the installed plugin - const supportedDefaultExtensionsConfiguration = Object.entries(WAZUH_DEFAULT_APP_CONFIG) + const supportedDefaultExtensionsConfiguration = Object.entries(getSettingsDefault()) .filter(([setting]) => setting.startsWith('extensions.')) .map(([setting, settingValue]) => { return [setting.split('.')[1], settingValue]; diff --git a/server/start/monitoring/index.ts b/server/start/monitoring/index.ts index e5e43853c8..a3c959800b 100644 --- a/server/start/monitoring/index.ts +++ b/server/start/monitoring/index.ts @@ -18,16 +18,11 @@ import { indexDate } from '../../lib/index-date'; import { buildIndexSettings } from '../../lib/build-index-settings'; import { WazuhHostsCtrl } from '../../controllers/wazuh-hosts'; import { - WAZUH_MONITORING_PATTERN, WAZUH_MONITORING_TEMPLATE_NAME, - WAZUH_MONITORING_DEFAULT_CREATION, - WAZUH_MONITORING_DEFAULT_ENABLED, - WAZUH_MONITORING_DEFAULT_FREQUENCY, - WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, - WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, } from '../../../common/constants'; import { tryCatchForIndexPermissionError } from '../tryCatchForIndexPermissionError'; import { delayAsPromise } from '../../../common/utils'; +import { getSettingDefaultValue } from '../../../common/services/settings'; const blueWazuh = '\u001b[34mwazuh\u001b[39m'; const monitoringErrorLogColors = [blueWazuh, 'monitoring', 'error']; @@ -56,12 +51,12 @@ function initMonitoringConfiguration(context){ MONITORING_ENABLED = appConfig && typeof appConfig['wazuh.monitoring.enabled'] !== 'undefined' ? appConfig['wazuh.monitoring.enabled'] && appConfig['wazuh.monitoring.enabled'] !== 'worker' - : WAZUH_MONITORING_DEFAULT_ENABLED; - MONITORING_FREQUENCY = getAppConfigurationSetting('wazuh.monitoring.frequency', appConfig, WAZUH_MONITORING_DEFAULT_FREQUENCY); + : getSettingDefaultValue('wazuh.monitoring.enabled'); + MONITORING_FREQUENCY = getAppConfigurationSetting('wazuh.monitoring.frequency', appConfig, getSettingDefaultValue('wazuh.monitoring.frequency')); MONITORING_CRON_FREQ = parseCron(MONITORING_FREQUENCY); - MONITORING_CREATION = getAppConfigurationSetting('wazuh.monitoring.creation', appConfig, WAZUH_MONITORING_DEFAULT_CREATION); + MONITORING_CREATION = getAppConfigurationSetting('wazuh.monitoring.creation', appConfig, getSettingDefaultValue('wazuh.monitoring.creation')); - MONITORING_INDEX_PATTERN = getAppConfigurationSetting('wazuh.monitoring.pattern', appConfig, WAZUH_MONITORING_PATTERN); + MONITORING_INDEX_PATTERN = getAppConfigurationSetting('wazuh.monitoring.pattern', appConfig, getSettingDefaultValue('wazuh.monitoring.pattern')); const lastCharIndexPattern = MONITORING_INDEX_PATTERN[MONITORING_INDEX_PATTERN.length - 1]; if (lastCharIndexPattern !== '*') { MONITORING_INDEX_PATTERN += '*'; @@ -131,7 +126,7 @@ async function checkTemplate(context) { monitoringTemplate.index_patterns = currentTemplate.body[WAZUH_MONITORING_TEMPLATE_NAME].index_patterns; }catch (error) { // Init with the default index pattern - monitoringTemplate.index_patterns = [WAZUH_MONITORING_PATTERN]; + monitoringTemplate.index_patterns = [getSettingDefaultValue('wazuh.monitoring.pattern')]; } // Check if the user is using a custom pattern and add it to the template if it does @@ -182,7 +177,7 @@ async function insertMonitoringDataElasticsearch(context, data) { const indexConfiguration = buildIndexSettings( appConfig, 'wazuh.monitoring', - WAZUH_MONITORING_DEFAULT_INDICES_SHARDS + getSettingDefaultValue('wazuh.monitoring.shards') ); // To update the index settings with this client is required close the index, update the settings and open it @@ -258,8 +253,8 @@ async function createIndex(context, indexName: string) { const IndexConfiguration = { settings: { index: { - number_of_shards: getAppConfigurationSetting('wazuh.monitoring.shards', appConfig, WAZUH_MONITORING_DEFAULT_INDICES_SHARDS), - number_of_replicas: getAppConfigurationSetting('wazuh.monitoring.replicas', appConfig, WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS) + number_of_shards: getAppConfigurationSetting('wazuh.monitoring.shards', appConfig, getSettingDefaultValue('wazuh.monitoring.shards')), + number_of_replicas: getAppConfigurationSetting('wazuh.monitoring.replicas', appConfig, getSettingDefaultValue('wazuh.monitoring.replicas')) } } }; diff --git a/test/functional/apps/overview/_integrity_monitoring.ts b/test/functional/apps/overview/_integrity_monitoring.ts index 588e83dd4e..8daebf4acd 100644 --- a/test/functional/apps/overview/_integrity_monitoring.ts +++ b/test/functional/apps/overview/_integrity_monitoring.ts @@ -13,7 +13,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../../../test/functional/ftr_provider_context'; import { SearchParams } from 'elasticsearch'; -import { WAZUH_ALERTS_PATTERN } from '../../../../common/constants'; +import { getSettingDefaultValue } from '../../../../common/services/settings'; export default function({getService, getPageObjects, }: FtrProviderContext) { const areaChart = getService('areaChart'); @@ -33,7 +33,7 @@ export default function({getService, getPageObjects, }: FtrProviderContext) { let es_index: string; before(async () => { await PageObjects.wazuhCommon.OpenIntegrityMonitoring(); - es_index = WAZUH_ALERTS_PATTERN; + es_index = getSettingDefaultValue('pattern'); }); beforeEach(async () => { diff --git a/test/functional/apps/overview/_security_events.ts b/test/functional/apps/overview/_security_events.ts index 50bf599f05..042137b813 100644 --- a/test/functional/apps/overview/_security_events.ts +++ b/test/functional/apps/overview/_security_events.ts @@ -13,7 +13,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../../../test/functional/ftr_provider_context'; import { SearchParams } from 'elasticsearch'; -import { WAZUH_ALERTS_PATTERN } from '../../../../common/constants'; +import { getSettingDefaultValue } from '../../../../common/services/settings'; export default function({getService, getPageObjects, }: FtrProviderContext) { const areaChart = getService('areaChart'); @@ -34,7 +34,7 @@ export default function({getService, getPageObjects, }: FtrProviderContext) { let es_index: string; before(async () => { await PageObjects.wazuhCommon.OpenSecurityEvents(); - es_index = WAZUH_ALERTS_PATTERN; + es_index = getSettingDefaultValue('pattern'); }); beforeEach(async () => { diff --git a/test/server/wazuh-elastic.js b/test/server/wazuh-elastic.js index 2143f211c4..0ebbed2ce4 100644 --- a/test/server/wazuh-elastic.js +++ b/test/server/wazuh-elastic.js @@ -1,6 +1,6 @@ const chai = require('chai'); const needle = require('needle'); -const { WAZUH_ALERTS_PATTERN, PLUGIN_PLATFORM_REQUEST_HEADERS } = require('../../common/constants'); +const { PLUGIN_PLATFORM_REQUEST_HEADERS } = require('../../common/constants'); const kibanaServer = process.env.KIBANA_IP || 'localhost'; @@ -15,7 +15,7 @@ describe('wazuh-elastic', () => { it('GET /elastic/known-fields/{pattern}', async () => { const res = await needle( 'get', - `${kibanaServer}:5601/elastic/known-fields/${WAZUH_ALERTS_PATTERN}`, + `${kibanaServer}:5601/elastic/known-fields/${getSettingDefaultValue('pattern')}`, {}, headers ); @@ -23,7 +23,7 @@ describe('wazuh-elastic', () => { res.body.output.should.be.a('object'); //res.body.output._index.should.be.eql('.kibana'); res.body.output._type.should.be.eql('doc'); - res.body.output._id.should.be.eql(`index-pattern:${WAZUH_ALERTS_PATTERN}`); + res.body.output._id.should.be.eql(`index-pattern:${getSettingDefaultValue('pattern')}`); }); }); @@ -31,7 +31,7 @@ describe('wazuh-elastic', () => { it('GET /elastic/visualizations/{tab}/{pattern}', async () => { const res = await needle( 'get', - `${kibanaServer}:5601/elastic/visualizations/overview-general/${WAZUH_ALERTS_PATTERN}`, + `${kibanaServer}:5601/elastic/visualizations/overview-general/${getSettingDefaultValue('pattern')}`, {}, headers ); @@ -46,7 +46,7 @@ describe('wazuh-elastic', () => { it('POST /elastic/visualizations/{tab}/{pattern}', async () => { const res = await needle( 'post', - `${kibanaServer}:5601/elastic/visualizations/cluster-monitoring/${WAZUH_ALERTS_PATTERN}`, + `${kibanaServer}:5601/elastic/visualizations/cluster-monitoring/${getSettingDefaultValue('pattern')}`, { nodes: { items: [], name: 'node01' } }, headers ); @@ -63,19 +63,19 @@ describe('wazuh-elastic', () => { it('GET /elastic/template/{pattern}', async () => { const res = await needle( 'get', - `${kibanaServer}:5601/elastic/template/${WAZUH_ALERTS_PATTERN}`, + `${kibanaServer}:5601/elastic/template/${getSettingDefaultValue('pattern')}`, {}, headers ); res.body.statusCode.should.be.eql(200); res.body.status.should.be.eql(true); - res.body.data.should.be.eql(`Template found for ${WAZUH_ALERTS_PATTERN}`); + res.body.data.should.be.eql(`Template found for ${getSettingDefaultValue('pattern')}`); }); it('GET /elastic/index-patterns/{pattern}', async () => { const res = await needle( 'get', - `${kibanaServer}:5601/elastic/index-patterns/${WAZUH_ALERTS_PATTERN}`, + `${kibanaServer}:5601/elastic/index-patterns/${getSettingDefaultValue('pattern')}`, {}, headers ); From c25a1a0d6840cb4a8a831d38b0f969d0c51e57ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 08:41:28 +0200 Subject: [PATCH 03/44] feat(settings): refactor the content of the default configuration file Use dynamically the definition of the plugin settings --- server/lib/initial-wazuh-config.ts | 295 +++++++---------------------- 1 file changed, 72 insertions(+), 223 deletions(-) diff --git a/server/lib/initial-wazuh-config.ts b/server/lib/initial-wazuh-config.ts index 9e99873a11..e485443f5a 100644 --- a/server/lib/initial-wazuh-config.ts +++ b/server/lib/initial-wazuh-config.ts @@ -11,45 +11,13 @@ */ import { - WAZUH_ALERTS_PATTERN, - WAZUH_DEFAULT_APP_CONFIG, - WAZUH_MONITORING_DEFAULT_CREATION, - WAZUH_MONITORING_DEFAULT_ENABLED, - WAZUH_MONITORING_DEFAULT_FREQUENCY, - WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, - WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, - WAZUH_MONITORING_PATTERN, - WAZUH_SAMPLE_ALERT_PREFIX, - WAZUH_STATISTICS_DEFAULT_CREATION, - WAZUH_STATISTICS_DEFAULT_CRON_FREQ, - WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, - WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, - WAZUH_STATISTICS_DEFAULT_NAME, - WAZUH_STATISTICS_DEFAULT_PREFIX, - WAZUH_STATISTICS_DEFAULT_STATUS, + PLUGIN_SETTINGS, + PLUGIN_SETTINGS_CATEGORIES, } from '../../common/constants'; import { webDocumentationLink } from '../../common/services/web_documentation'; import { configEquivalences } from '../../common/config-equivalences'; -/** - * Given a string, this function builds a multine string, each line about 70 - * characters long, splitted at the closest whitespace character to that lentgh. - * - * This function is used to transform the settings description stored in the - * configEquivalences map into a multiline string to be used as the setting - * documentation. - * - * The # character is also appended to the beginning of each line. - * - * @param text - * @returns multine string - */ -function splitDescription(text: string = ''): string { - const lines = text.match(/.{1,80}(?=\s|$)/g) || []; - return lines.map((z) => '# ' + z.trim()).join('\n'); -} - -export const initialWazuhConfig: string = `--- +const header: string = `--- # # Wazuh app - App configuration file # Copyright (C) 2015-2022 Wazuh, Inc. @@ -67,194 +35,56 @@ export const initialWazuhConfig: string = `--- # ${webDocumentationLink('user-manual/wazuh-dashboard/config-file.html')} # # Also, you can check our repository: -# https://github.com/wazuh/wazuh-kibana-app -# -# ---------------------------- Unauthorized roles ------------------------------ -# -# Disable Wazuh for the Elasticsearch / OpenSearch roles defined here. -# disabled_roles: -# - wazuh_disabled -# -# ------------------------------- Index patterns ------------------------------- -# -${splitDescription(configEquivalences.pattern)} -# pattern: ${WAZUH_ALERTS_PATTERN} -# -# ----------------------------------- Checks ----------------------------------- -# -# Define which checks will be executed by the App's HealthCheck. -# Allowed values are: true, false -# -${splitDescription(configEquivalences['checks.pattern'])} -# checks.pattern: ${WAZUH_DEFAULT_APP_CONFIG['checks.pattern']} -# -${splitDescription(configEquivalences['checks.template'])} -# checks.template: ${WAZUH_DEFAULT_APP_CONFIG['checks.template']} -# -${splitDescription(configEquivalences['checks.api'])} -# checks.api: ${WAZUH_DEFAULT_APP_CONFIG['checks.api']} -# -${splitDescription(configEquivalences['checks.setup'])} -# checks.setup: ${WAZUH_DEFAULT_APP_CONFIG['checks.setup']} -# -${splitDescription(configEquivalences['checks.fields'])} -# checks.fields: ${WAZUH_DEFAULT_APP_CONFIG['checks.fields']} -# -${splitDescription(configEquivalences['checks.metaFields'])} -# checks.metaFields: ${WAZUH_DEFAULT_APP_CONFIG['checks.metaFields']} -# -${splitDescription(configEquivalences['checks.timeFilter'])} -# checks.timeFilter: ${WAZUH_DEFAULT_APP_CONFIG['checks.timeFilter']} -# -${splitDescription(configEquivalences['checks.maxBuckets'])} -# checks.maxBuckets: ${WAZUH_DEFAULT_APP_CONFIG['checks.maxBuckets']} -# -# --------------------------------- Extensions --------------------------------- -# -# Define the initial state of the extensions (enabled / disabled) for recently -# added hosts. The extensions can be enabled or disabled anytime using the UI. -# Allowed values are: true, false -# -${splitDescription(configEquivalences['extensions.pci'])} -# extensions.pci: ${WAZUH_DEFAULT_APP_CONFIG['extensions.pci']} -# -${splitDescription(configEquivalences['extensions.gdpr'])} -# extensions.gdpr: ${WAZUH_DEFAULT_APP_CONFIG['extensions.gdpr']} -# -${splitDescription(configEquivalences['extensions.hipaa'])} -# extensions.hipaa: ${WAZUH_DEFAULT_APP_CONFIG['extensions.hipaa']} -# -${splitDescription(configEquivalences['extensions.nist'])} -# extensions.nist: ${WAZUH_DEFAULT_APP_CONFIG['extensions.nist']} -# -${splitDescription(configEquivalences['extensions.tsc'])} -# extensions.tsc: ${WAZUH_DEFAULT_APP_CONFIG['extensions.tsc']} -# -${splitDescription(configEquivalences['extensions.audit'])} -# extensions.audit: ${WAZUH_DEFAULT_APP_CONFIG['extensions.audit']} -# -${splitDescription(configEquivalences['extensions.oscap'])} -# extensions.oscap: ${WAZUH_DEFAULT_APP_CONFIG['extensions.oscap']} -# -${splitDescription(configEquivalences['extensions.ciscat'])} -# extensions.ciscat: ${WAZUH_DEFAULT_APP_CONFIG['extensions.ciscat']} -# -${splitDescription(configEquivalences['extensions.aws'])} -# extensions.aws: ${WAZUH_DEFAULT_APP_CONFIG['extensions.aws']} -# -${splitDescription(configEquivalences['extensions.gcp'])} -# extensions.gcp: ${WAZUH_DEFAULT_APP_CONFIG['extensions.gcp']} -# -${splitDescription(configEquivalences['extensions.virustotal'])} -# extensions.virustotal: ${WAZUH_DEFAULT_APP_CONFIG['extensions.virustotal']} -# -${splitDescription(configEquivalences['extensions.osquery'])} -# extensions.osquery: ${WAZUH_DEFAULT_APP_CONFIG['extensions.osquery']} -# -${splitDescription(configEquivalences['extensions.docker'])} -# extensions.docker: ${WAZUH_DEFAULT_APP_CONFIG['extensions.docker']} -# -# ------------------------------- Timeout -------------------------------------- -# -${splitDescription(configEquivalences.timeout)} -# timeout: ${WAZUH_DEFAULT_APP_CONFIG.timeout} -# -# --------------------------- Index pattern selector --------------------------- -# -${splitDescription(configEquivalences['ip.selector'])} -# ip.selector: ${WAZUH_DEFAULT_APP_CONFIG['ip.selector']} -# -${splitDescription(configEquivalences['ip.ignore'])} -# ip.ignore: ${WAZUH_DEFAULT_APP_CONFIG['ip.ignore']} -# -# ------------------------------ Monitoring ------------------------------------ -# -${splitDescription(configEquivalences['wazuh.monitoring.enabled'])} -# wazuh.monitoring.enabled: ${WAZUH_MONITORING_DEFAULT_ENABLED} -# -${splitDescription(configEquivalences['wazuh.monitoring.frequency'])} -# wazuh.monitoring.frequency: ${WAZUH_MONITORING_DEFAULT_FREQUENCY} -# -${splitDescription(configEquivalences['wazuh.monitoring.shards'])} -# wazuh.monitoring.shards: ${WAZUH_MONITORING_DEFAULT_INDICES_SHARDS} -# -${splitDescription(configEquivalences['wazuh.monitoring.replicas'])} -# wazuh.monitoring.replicas: ${WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS} -# -${splitDescription(configEquivalences['wazuh.monitoring.creation'])} -# Allowed values are: h (hourly), d (daily), w (weekly), m (monthly) -# wazuh.monitoring.creation: ${WAZUH_MONITORING_DEFAULT_CREATION} -# -${splitDescription(configEquivalences['wazuh.monitoring.pattern'])} -# wazuh.monitoring.pattern: ${WAZUH_MONITORING_PATTERN} -# -# --------------------------------- Sample data -------------------------------- -# -${splitDescription(configEquivalences['alerts.sample.prefix'])} -# alerts.sample.prefix: ${WAZUH_SAMPLE_ALERT_PREFIX} -# -# ------------------------------ Background tasks ------------------------------ -# -${splitDescription(configEquivalences['cron.prefix'])} -# cron.prefix: ${WAZUH_STATISTICS_DEFAULT_PREFIX} -# -# ------------------------------ Wazuh Statistics ------------------------------ -# -${splitDescription(configEquivalences['cron.statistics.status'])} -# cron.statistics.status: ${WAZUH_STATISTICS_DEFAULT_STATUS} -# -${splitDescription(configEquivalences['cron.statistics.apis'])} -# cron.statistics.apis: ${WAZUH_DEFAULT_APP_CONFIG['cron.statistics.apis']} -# -${splitDescription(configEquivalences['cron.statistics.interval'])} -# cron.statistics.interval: ${WAZUH_STATISTICS_DEFAULT_CRON_FREQ} -# -${splitDescription(configEquivalences['cron.statistics.index.name'])} -# cron.statistics.index.name: ${WAZUH_STATISTICS_DEFAULT_NAME} -# -${splitDescription(configEquivalences['cron.statistics.index.creation'])} -# cron.statistics.index.creation: ${WAZUH_STATISTICS_DEFAULT_CREATION} -# -${splitDescription(configEquivalences['cron.statistics.index.shards'])} -# cron.statistics.shards: ${WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS} -# -${splitDescription(configEquivalences['cron.statistics.index.replicas'])} -# cron.statistics.replicas: ${WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS} -# -# ------------------------------ Logo customization ---------------------------- -# -${splitDescription(configEquivalences['customization.logo.app'])} -# customization.logo.app: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.app']} -# -${splitDescription(configEquivalences['customization.logo.sidebar'])} -# customization.logo.sidebar: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.sidebar']} -# -${splitDescription(configEquivalences['customization.logo.healthcheck'])} -# customization.logo.healthcheck: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.healthcheck']} -# -${splitDescription(configEquivalences['customization.logo.reports'])} -# customization.logo.reports: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.reports']} -# -# ---------------------------- Hide manager alerts ----------------------------- -# -${splitDescription(configEquivalences.hideManagerAlerts)} -# hideManagerAlerts: ${WAZUH_DEFAULT_APP_CONFIG.hideManagerAlerts} -# -# ------------------------------- App logging level ---------------------------- -# -${splitDescription(configEquivalences['logs.level'])} -# Allowed values are: info, debug -# logs.level: ${WAZUH_DEFAULT_APP_CONFIG['logs.level']} -# -# ------------------------------- Agent enrollment ----------------------------- -# -${splitDescription(configEquivalences['enrollment.dns'])} -# enrollment.dns: ${WAZUH_DEFAULT_APP_CONFIG['enrollment.dns']} -# -${splitDescription(configEquivalences['enrollment.password'])} -# enrollment.password: ${WAZUH_DEFAULT_APP_CONFIG['enrollment.password']} -# -#-------------------------------- Wazuh hosts ---------------------------------- +# https://github.com/wazuh/wazuh-kibana-app`; + +const pluginSettingsConfiguration = Object.entries(PLUGIN_SETTINGS_CATEGORIES).map(([pluginSettingCategoryID, pluginSettingCategoryConfiguration]) => { + const header = `#------------------------------- ${pluginSettingCategoryConfiguration.title} -------------------------------`; + const description = pluginSettingCategoryConfiguration.description + /* + # category description + # + */ + ? splitDescription(pluginSettingCategoryConfiguration.description) + : ''; + + const pluginSettingsCategory = Object.entries(PLUGIN_SETTINGS) + .filter(([, {category, configurableFile}]) => configurableFile && category.toString() === pluginSettingCategoryID) + .map(([pluginSettingKey, {description, default: defaultValue, options = {}}] ) => + /* + # setting description + # settingKey: settingValue + */ + [splitDescription(description), `# ${pluginSettingKey}: ${printSettingValue(defaultValue)}`].join('\n') + ).join('\n#\n'); + /* + #------------------- category name -------------- + # + # category description + # + # setting description + # settingKey: settingValue + # + # setting description + # settingKey: settingValue + # ... + */ + return [header, description, pluginSettingsCategory].join('\n#\n'); +}).join('\n#\n'); + + +function printSettingValue(value: any){ + if(typeof value === 'object'){ + return JSON.stringify(value) + }; + + if(typeof value === 'string' && value.length === 0){ + return `''` + }; + + return value; +}; + +const hostsConfiguration = `#-------------------------------- Wazuh hosts ---------------------------------- # # The following configuration is the default structure to define a host. # @@ -286,3 +116,22 @@ hosts: password: wazuh-wui run_as: false `; + +/** + * Given a string, this function builds a multine string, each line about 70 + * characters long, splitted at the closest whitespace character to that lentgh. + * + * This function is used to transform the settings description + * into a multiline string to be used as the setting documentation. + * + * The # character is also appended to the beginning of each line. + * + * @param text + * @returns multine string + */ + function splitDescription(text: string = ''): string { + const lines = text.match(/.{1,80}(?=\s|$)/g) || []; + return lines.map((z) => '# ' + z.trim()).join('\n'); +} + +export const initialWazuhConfig: string = [header, pluginSettingsConfiguration, hostsConfiguration].join('\n'); From ffdb2d7f0f721e20c6cbc1eeefb80f166f2c0127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 08:52:54 +0200 Subject: [PATCH 04/44] feat(inputs): create new inputs components Add new hooks to manage when a input value or form has changed Add new inputs components --- public/components/common/form/hooks.tsx | 70 +++++++++++++++++++ public/components/common/form/index.tsx | 58 +++++++++++++++ .../components/common/form/input_editor.tsx | 17 +++++ .../components/common/form/input_number.tsx | 16 +++++ .../components/common/form/input_select.tsx | 15 ++++ .../components/common/form/input_switch.tsx | 15 ++++ public/components/common/form/input_text.tsx | 16 +++++ public/components/common/form/types.ts | 17 +++++ 8 files changed, 224 insertions(+) create mode 100644 public/components/common/form/hooks.tsx create mode 100644 public/components/common/form/index.tsx create mode 100644 public/components/common/form/input_editor.tsx create mode 100644 public/components/common/form/input_number.tsx create mode 100644 public/components/common/form/input_select.tsx create mode 100644 public/components/common/form/input_switch.tsx create mode 100644 public/components/common/form/input_text.tsx create mode 100644 public/components/common/form/types.ts diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx new file mode 100644 index 0000000000..4fafe6512d --- /dev/null +++ b/public/components/common/form/hooks.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import _ from 'lodash'; +import { EpluginSettingType } from '../../../../common/constants'; + +function getValueFromEvent(event, type){ + return getValueFromEventType?.[type]?.(event) || getValueFromEventType.default(event) +}; + +type TuseFormFieldChanged = { + onChange?: any + transformUIInputValue?: (inputValue: any) => any + type: string + validate?: (currentValue: any) => string | undefined +} +const getValueFromEventType = { + [EpluginSettingType.switch] : (event: any) => event.target.checked, + [EpluginSettingType.editor]: (value: any) => value, + default: (event: any) => event.target.value, +} + +export const useFormFieldChanged = (field, initialValue: any, { onChange: onChangeFormField, transformUIInputValue, type, validate }: TuseFormFieldChanged) => { + const [value, setValue] = useState(initialValue); + const [validationError, setValidationError] = useState(null); + + function onChange(event){ + const inputValue = getValueFromEvent(event, type); + const currentValue = transformUIInputValue ? transformUIInputValue(inputValue) : inputValue; + const error = validate ? validate(currentValue) : false; + setValue(currentValue); + validationError !== error && setValidationError(error); + onChangeFormField && onChangeFormField({field, changed: !_.isEqual(initialValue, currentValue), previousValue: value, currentValue, error}); + }; + + function resetValue(){ + setValue(initialValue); + setValidationError(null); + }; + + return { value, error: validationError, onChange, resetValue }; +}; + +export const useFormChanged = () => { + const [formFieldsChanged, setFormFieldsChanged] = useState({}); + + function onChangeFormField({ field, changed, currentValue, error }: { field: string, changed: boolean, currentValue: any, error: any}){ + if(changed){ + setFormFieldsChanged(state => ({...state, [field]: {currentValue, error}})); + }else{ + onFormFieldReset(field); + }; + }; + + const fieldsCount: number = Object.keys(formFieldsChanged).length; + const fieldsSuccessCount: number = Object.entries(formFieldsChanged).filter(([_, {error}]) => !error).length; + const fieldsErrorCount: number = fieldsCount - fieldsSuccessCount; + const isValid = fieldsCount === fieldsSuccessCount; + + function onFormFieldReset(field?: string){ + if(field){ + setFormFieldsChanged(state => { + const { [field]: _, ...rest } = state; + return {...rest}; + }); + }else{ + setFormFieldsChanged({}); + }; + }; + + return { onChangeFormField, fieldsCount, fieldsSuccessCount, fieldsErrorCount, fields: formFieldsChanged, onFormFieldReset, isValid }; +}; diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx new file mode 100644 index 0000000000..2dd286dba4 --- /dev/null +++ b/public/components/common/form/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { IInputForm } from './types'; +import { InputFormEditor } from './input_editor'; +import { InputFormNumber } from './input_number'; +import { InputFormText } from './input_text'; +import { InputFormSwitch } from './input_switch'; +import { InputFormSelect } from './input_select'; +import { useFormFieldChanged } from './hooks'; +import { + EuiFormRow, +} from '@elastic/eui'; + +export const InputForm = (props: IInputForm) => { + const { field, label = null, initialValue, onChange: onChangeInputForm, preInput = null, postInput = null } = props; + const { value, error, onChange } = useFormFieldChanged( + field.key, + initialValue, + { validate: field?.validate, onChange: onChangeInputForm, type: field.type, transformUIInputValue: field?.transformUIInputValue } + ); + + const ComponentInput = Input[field.type]; + + if(!ComponentInput){ + return null; + }; + + const isInvalid = Boolean(error); + + const input = ( + + ); + + return label + ? ( + + <> + {typeof preInput === 'function' ? preInput({value, error}) : preInput} + {input} + {typeof postInput === 'function' ? postInput({value, error}) : postInput} + + ) + : input; + +}; + +const Input = { + switch: InputFormSwitch, + editor: InputFormEditor, + number: InputFormNumber, + select: InputFormSelect, + text: InputFormText, +}; diff --git a/public/components/common/form/input_editor.tsx b/public/components/common/form/input_editor.tsx new file mode 100644 index 0000000000..2d8f29892d --- /dev/null +++ b/public/components/common/form/input_editor.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { + EuiCodeEditor, +} from '@elastic/eui'; +import { IInputFormType } from './types'; + +export const InputFormEditor = ({field, value, onChange}: IInputFormType) => { + return ( + + ); +}; diff --git a/public/components/common/form/input_number.tsx b/public/components/common/form/input_number.tsx new file mode 100644 index 0000000000..01d93a4cc8 --- /dev/null +++ b/public/components/common/form/input_number.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { + EuiFieldNumber, +} from '@elastic/eui'; +import { IInputFormType } from './types'; + +export const InputFormNumber = ({ field, value, onChange }: IInputFormType) => { + return ( + + ); +}; diff --git a/public/components/common/form/input_select.tsx b/public/components/common/form/input_select.tsx new file mode 100644 index 0000000000..7edce9ffc0 --- /dev/null +++ b/public/components/common/form/input_select.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { + EuiSelect, +} from '@elastic/eui'; +import { IInputFormType } from './types'; + +export const InputFormSelect = ({ field, value, onChange }: IInputFormType) => { + return ( + + ) +}; diff --git a/public/components/common/form/input_switch.tsx b/public/components/common/form/input_switch.tsx new file mode 100644 index 0000000000..b668e71aec --- /dev/null +++ b/public/components/common/form/input_switch.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { + EuiSwitch, +} from '@elastic/eui'; +import { IInputFormType } from './types'; + +export const InputFormSwitch = ({ field, value, onChange }: IInputFormType) => { + return ( + + ); +}; diff --git a/public/components/common/form/input_text.tsx b/public/components/common/form/input_text.tsx new file mode 100644 index 0000000000..994ac62452 --- /dev/null +++ b/public/components/common/form/input_text.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { + EuiFieldText, +} from '@elastic/eui'; +import { IInputFormType } from "./types"; + +export const InputFormText = ({ value, isInvalid, onChange }: IInputFormType) => { + return ( + + ); +}; diff --git a/public/components/common/form/types.ts b/public/components/common/form/types.ts new file mode 100644 index 0000000000..16b5f25d4f --- /dev/null +++ b/public/components/common/form/types.ts @@ -0,0 +1,17 @@ +import { TPluginSettingWithKey } from "../../../../common/constants"; + +export interface IInputFormType { + field: TPluginSettingWithKey + value: any + onChange: (event: any) => void + isInvalid?: boolean +}; + +export interface IInputForm { + field: TPluginSettingWithKey + initialValue: any + onChange: (event: any) => void + label?: string + preInput?: ((options: {value: any, error: string | null}) => JSX.Element) + postInput?: ((options: {value: any, error: string | null}) => JSX.Element) +}; From e7cd9b0329b5289f3cf62dd7e96351297f99eaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 09:20:08 +0200 Subject: [PATCH 05/44] feat(configuration): refactor the form of Settings/Configuration Refactor Header, BottomBar, Configuration components Remove deprecated files --- common/config-equivalences.js | 234 --------------- common/services/settings.ts | 20 ++ .../configuration/components/bottom-bar.tsx | 136 ++------- .../components/categories/categories.tsx | 46 --- .../components/category/category.tsx | 87 ++++-- .../__snapshots__/field-form.test.tsx.snap | 67 ----- .../category/components/field-form.test.tsx | 39 --- .../category/components/field-form.tsx | 175 ----------- .../components/category/components/index.ts | 14 - .../components/categories/index.ts | 13 - .../configuration/components/header.tsx | 42 ++- .../configuration/components/index.ts | 1 - .../settings/configuration/configuration.tsx | 275 +++++++++++++++--- .../utils/configuration-handler.js | 34 --- server/lib/initial-wazuh-config.ts | 1 - 15 files changed, 353 insertions(+), 831 deletions(-) delete mode 100644 common/config-equivalences.js delete mode 100644 public/components/settings/configuration/components/categories/categories.tsx delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/field-form.tsx delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/index.ts delete mode 100644 public/components/settings/configuration/components/categories/index.ts delete mode 100644 public/components/settings/configuration/utils/configuration-handler.js diff --git a/common/config-equivalences.js b/common/config-equivalences.js deleted file mode 100644 index 0441e3f8b1..0000000000 --- a/common/config-equivalences.js +++ /dev/null @@ -1,234 +0,0 @@ -import { ASSETS_PUBLIC_URL, PLUGIN_PLATFORM_NAME } from "./constants"; - -export const configEquivalences = { - pattern: "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.", - 'customization.logo.app':`Set the name of the app logo stored at ${ASSETS_PUBLIC_URL}`, - 'customization.logo.sidebar':`Set the name of the sidebar logo stored at ${ASSETS_PUBLIC_URL}`, - 'customization.logo.healthcheck':`Set the name of the health-check logo stored at ${ASSETS_PUBLIC_URL}`, - 'customization.logo.reports':`Set the name of the reports logo (.png) stored at ${ASSETS_PUBLIC_URL}`, - 'checks.pattern': - 'Enable or disable the index pattern health check when opening the app.', - 'checks.template': - 'Enable or disable the template health check when opening the app.', - 'checks.api': 'Enable or disable the API health check when opening the app.', - 'checks.setup': - 'Enable or disable the setup health check when opening the app.', - 'checks.fields': - 'Enable or disable the known fields health check when opening the app.', - 'checks.metaFields': - `Change the default value of the ${PLUGIN_PLATFORM_NAME} metaField configuration`, - 'checks.timeFilter': - `Change the default value of the ${PLUGIN_PLATFORM_NAME} timeFilter configuration`, - 'checks.maxBuckets': - `Change the default value of the ${PLUGIN_PLATFORM_NAME} max buckets configuration`, - 'extensions.pci': 'Enable or disable the PCI DSS tab on Overview and Agents.', - 'extensions.gdpr': 'Enable or disable the GDPR tab on Overview and Agents.', - 'extensions.hipaa': 'Enable or disable the HIPAA tab on Overview and Agents.', - 'extensions.nist': 'Enable or disable the NIST 800-53 tab on Overview and Agents.', - 'extensions.tsc': 'Enable or disable the TSC tab on Overview and Agents.', - 'extensions.audit': 'Enable or disable the Audit tab on Overview and Agents.', - 'extensions.oscap': - 'Enable or disable the Open SCAP tab on Overview and Agents.', - 'extensions.ciscat': - 'Enable or disable the CIS-CAT tab on Overview and Agents.', - 'extensions.aws': 'Enable or disable the Amazon (AWS) tab on Overview.', - 'extensions.gcp': 'Enable or disable the Google Cloud Platform tab on Overview.', - 'extensions.virustotal': - 'Enable or disable the VirusTotal tab on Overview and Agents.', - 'extensions.osquery': - 'Enable or disable the Osquery tab on Overview and Agents.', - 'extensions.mitre': 'Enable or disable the MITRE tab on Overview and Agents.', - 'extensions.docker': - 'Enable or disable the Docker listener tab on Overview and Agents.', - timeout: - 'Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.', - 'ip.selector': - 'Define if the user is allowed to change the selected index pattern directly from the top menu bar.', - 'ip.ignore': - 'Disable certain index pattern names from being available in index pattern selector from the Wazuh app.', - 'wazuh.monitoring.enabled': - 'Enable or disable the wazuh-monitoring index creation and/or visualization.', - 'wazuh.monitoring.frequency': - 'Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.', - 'wazuh.monitoring.shards': - 'Define the number of shards to use for the wazuh-monitoring-* indices.', - 'wazuh.monitoring.replicas': - 'Define the number of replicas to use for the wazuh-monitoring-* indices.', - 'wazuh.monitoring.creation': - 'Define the interval in which a new wazuh-monitoring index will be created.', - 'wazuh.monitoring.pattern': - 'Default index pattern to use for Wazuh monitoring.', - hideManagerAlerts: - 'Hide the alerts of the manager in every dashboard.', - 'logs.level': - 'Logging level of the App.', - 'enrollment.dns': - 'Specifies the Wazuh registration server, used for the agent enrollment.', - 'enrollment.password': - 'Specifies the password used to authenticate during the agent enrollment.', - 'cron.prefix': - 'Define the index prefix of predefined jobs.', - 'cron.statistics.status': - 'Enable or disable the statistics tasks.', - 'cron.statistics.apis': - 'Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.', - 'cron.statistics.interval': 'Define the frequency of task execution using cron schedule expressions.', - 'cron.statistics.index.name': 'Define the name of the index in which the documents will be saved.', - 'cron.statistics.index.creation': 'Define the interval in which a new index will be created.', - 'cron.statistics.index.shards': 'Define the number of shards to use for the statistics indices.', - 'cron.statistics.index.replicas': 'Define the number of replicas to use for the statistics indices.', - 'alerts.sample.prefix': 'Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.', -}; - -export const nameEquivalence = { - pattern: 'Index pattern', - 'customization.logo.app': 'Logo App', - 'customization.logo.sidebar': 'Logo Sidebar', - 'customization.logo.healthcheck': 'Logo Health Check', - 'customization.logo.reports': 'Logo Reports', - 'checks.pattern': 'Index pattern', - 'checks.template': 'Index template', - 'checks.api': 'API connection', - 'checks.setup': 'API version', - 'checks.fields': 'Known fields', - 'checks.metaFields': 'Remove meta fields', - 'checks.timeFilter': 'Set time filter to 24h', - 'checks.maxBuckets': 'Set max buckets to 200000', - timeout: 'Request timeout', - 'ip.selector': 'IP selector', - 'ip.ignore': 'IP ignore', - 'xpack.rbac.enabled': 'X-Pack RBAC', - 'wazuh.monitoring.enabled': 'Status', - 'wazuh.monitoring.frequency': 'Frequency', - 'wazuh.monitoring.shards': 'Index shards', - 'wazuh.monitoring.replicas': 'Index replicas', - 'wazuh.monitoring.creation': 'Index creation', - 'wazuh.monitoring.pattern': 'Index pattern', - hideManagerAlerts: 'Hide manager alerts', - 'logs.level': 'Log level', - 'enrollment.dns': 'Enrollment DNS', - 'cron.prefix': 'Cron prefix', - 'cron.statistics.status': 'Status', - 'cron.statistics.apis': 'Includes apis', - 'cron.statistics.interval': 'Interval', - 'cron.statistics.index.name': 'Index name', - 'cron.statistics.index.creation': 'Index creation', - 'cron.statistics.index.shards': 'Index shards', - 'cron.statistics.index.replicas': 'Index replicas', - 'alerts.sample.prefix': 'Sample alerts prefix', -} - -const HEALTH_CHECK = 'Health Check'; -const GENERAL = 'General'; -const SECURITY = 'Security'; -const MONITORING = 'Monitoring'; -const STATISTICS = 'Statistics'; -const CUSTOMIZATION = 'Logo Customization'; -export const categoriesNames = [HEALTH_CHECK, GENERAL, SECURITY, MONITORING, STATISTICS, CUSTOMIZATION]; - -export const categoriesEquivalence = { - pattern: GENERAL, - 'customization.logo.app':CUSTOMIZATION, - 'customization.logo.sidebar':CUSTOMIZATION, - 'customization.logo.healthcheck':CUSTOMIZATION, - 'customization.logo.reports':CUSTOMIZATION, - 'checks.pattern': HEALTH_CHECK, - 'checks.template': HEALTH_CHECK, - 'checks.api': HEALTH_CHECK, - 'checks.setup': HEALTH_CHECK, - 'checks.fields': HEALTH_CHECK, - 'checks.metaFields': HEALTH_CHECK, - 'checks.timeFilter': HEALTH_CHECK, - 'checks.maxBuckets': HEALTH_CHECK, - timeout: GENERAL, - 'ip.selector': GENERAL, - 'ip.ignore': GENERAL, - 'wazuh.monitoring.enabled': MONITORING, - 'wazuh.monitoring.frequency': MONITORING, - 'wazuh.monitoring.shards': MONITORING, - 'wazuh.monitoring.replicas': MONITORING, - 'wazuh.monitoring.creation': MONITORING, - 'wazuh.monitoring.pattern': MONITORING, - hideManagerAlerts: GENERAL, - 'logs.level': GENERAL, - 'enrollment.dns': GENERAL, - 'cron.prefix': GENERAL, - 'cron.statistics.status': STATISTICS, - 'cron.statistics.apis': STATISTICS, - 'cron.statistics.interval': STATISTICS, - 'cron.statistics.index.name': STATISTICS, - 'cron.statistics.index.creation': STATISTICS, - 'cron.statistics.index.shards': STATISTICS, - 'cron.statistics.index.replicas': STATISTICS, - 'alerts.sample.prefix': GENERAL, -} - -const TEXT = 'text'; -const NUMBER = 'number'; -const LIST = 'list'; -const BOOLEAN = 'boolean'; -const ARRAY = 'array'; -const INTERVAL = 'interval' - -export const formEquivalence = { - pattern: { type: TEXT }, - 'customization.logo.app': { type: TEXT }, - 'customization.logo.sidebar': { type: TEXT }, - 'customization.logo.healthcheck': { type: TEXT }, - 'customization.logo.reports': { type: TEXT }, - 'checks.pattern': { type: BOOLEAN }, - 'checks.template': { type: BOOLEAN }, - 'checks.api': { type: BOOLEAN }, - 'checks.setup': { type: BOOLEAN }, - 'checks.fields': { type: BOOLEAN }, - 'checks.metaFields': { type: BOOLEAN }, - 'checks.timeFilter': { type: BOOLEAN }, - 'checks.maxBuckets': { type: BOOLEAN }, - timeout: { type: NUMBER }, - 'ip.selector': { type: BOOLEAN }, - 'ip.ignore': { type: ARRAY }, - 'xpack.rbac.enabled': { type: BOOLEAN }, - 'wazuh.monitoring.enabled': { type: BOOLEAN }, - 'wazuh.monitoring.frequency': { type: NUMBER }, - 'wazuh.monitoring.shards': { type: NUMBER }, - 'wazuh.monitoring.replicas': { type: NUMBER }, - 'wazuh.monitoring.creation': { - type: LIST, params: { - options: [ - { text: 'Hourly', value: 'h' }, - { text: 'Daily', value: 'd' }, - { text: 'Weekly', value: 'w' }, - { text: 'Monthly', value: 'm' }, - ] - } - }, - 'wazuh.monitoring.pattern': { type: TEXT }, - hideManagerAlerts: { type: BOOLEAN }, - 'logs.level': { - type: LIST, params: { - options: [ - { text: 'Info', value: 'info' }, - { text: 'Debug', value: 'debug' }, - ] - } - }, - 'enrollment.dns': { type: TEXT }, - 'cron.prefix': { type: TEXT }, - 'cron.statistics.status': { type: BOOLEAN }, - 'cron.statistics.apis': { type: ARRAY }, - 'cron.statistics.interval': { type: INTERVAL }, - 'cron.statistics.index.name': { type: TEXT }, - 'cron.statistics.index.creation': { - type: LIST, params: { - options: [ - { text: 'Hourly', value: 'h' }, - { text: 'Daily', value: 'd' }, - { text: 'Weekly', value: 'w' }, - { text: 'Monthly', value: 'm' }, - ] - } - }, - 'cron.statistics.index.shards': { type: NUMBER }, - 'cron.statistics.index.replicas': { type: NUMBER }, - 'alerts.sample.prefix': { type: TEXT }, -} diff --git a/common/services/settings.ts b/common/services/settings.ts index 5f0c532922..d508c8ecf7 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -31,3 +31,23 @@ export function getSettingsDefaultList() { { ...pluginSettingConfiguration, key: pluginSettingID } ]), []); }; + +/** + * + * @param pluginSetting Plugin setting definition + * @param fromValue value of the form + * @returns Transform the form value to the type of the setting expected + */ + export function formatSettingValueFromForm(pluginSettingKey: string, formValue: any) { + const { type } = PLUGIN_SETTINGS[pluginSettingKey]; + return formatSettingValueFromFormType[type](formValue); +}; + +const formatSettingValueFromFormType = { + [EpluginSettingType.text]: (value: string): string => value, + [EpluginSettingType.textarea]: (value: string): string => value, + [EpluginSettingType.number]: (value: string): number => Number(value), + [EpluginSettingType.switch]: (value: string): boolean => Boolean(value), + [EpluginSettingType.editor]: (value: any): any => value, // Array form transforms the value. It is coming a valid JSON. + [EpluginSettingType.select]: (value: any): any => value, +}; \ No newline at end of file diff --git a/public/components/settings/configuration/components/bottom-bar.tsx b/public/components/settings/configuration/components/bottom-bar.tsx index 6ea078f057..34cabaf53e 100644 --- a/public/components/settings/configuration/components/bottom-bar.tsx +++ b/public/components/settings/configuration/components/bottom-bar.tsx @@ -11,7 +11,7 @@ * Find more information about this on the LICENSE file. */ -import React, { } from 'react'; +import React from 'react'; import ConfigurationHandler from '../utils/configuration-handler'; //@ts-ignore import { getToasts } from '../../../../kibana-services'; @@ -35,39 +35,32 @@ import { import { getErrorOrchestrator } from '../../../../react-services/common-services'; interface IBottomBarProps { - updatedConfig: { [setting: string]: string | number | boolean | object } - setUpdateConfig(setting: {}): void - setLoading(loading: boolean): void - config: ISetting[] + totalCount: number + errorsCount: number + onCancel: () => void + onSave: () => void } -export const BottomBar: React.FunctionComponent = ({ updatedConfig, setUpdateConfig, setLoading, config }) => { - return (!!Object.keys(updatedConfig).length - ? +export const BottomBar: React.FunctionComponent = ({ totalCount, errorsCount, onCancel, onSave }) => ( + - - - + + + - : null - ); -} +); -const SettingLabel = ({ updatedConfig }) => ( +const SettingLabel = ({ count, }) => ( - {`${Object.keys(updatedConfig).length} unsaved settings`} + {`${count} unsaved settings`} ); -const CancelButton = ({ setUpdateConfig }) => ( +const CancelButton = ({ onClick }) => ( ( iconType='cross' color="ghost" className="mgtAdvancedSettingsForm__button" - onClick={() => setUpdateConfig({})}> + onClick={onClick}> Cancel changes -) +); -const SaveButton = ({ updatedConfig, setUpdateConfig, setLoading, config }) => ( +const SaveButton = ({ onClick }) => ( ( iconType='check' color='secondary' className="mgtAdvancedSettingsForm__button" - onClick={() => saveSettings(updatedConfig, setUpdateConfig, setLoading, config)} > + onClick={onClick} > Save changes -) - -const saveSettings = async (updatedConfig: {}, setUpdateConfig: Function, setLoading: Function, config: ISetting[]) => { - setLoading(true); - try { - await Promise.all(Object.keys(updatedConfig).map(async setting => await saveSetting(setting, updatedConfig, config))); - successToast(); - setUpdateConfig({}); - } catch (error) { - const options: UIErrorLog = { - context: `${BottomBar.name}.saveSettings`, - level: UI_LOGGER_LEVELS.ERROR as UILogLevel, - severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity, - store: true, - error: { - error: error, - message: error.message || error, - title: `Error saving the configuration: ${error.message || error}`, - }, - }; - - getErrorOrchestrator().handleError(options); - } finally { - setLoading(false); - } -} - -const saveSetting = async (setting, updatedConfig, config: ISetting[]) => { - try { - (config.find(item => item.setting === setting) || { value: '' }).value = updatedConfig[setting]; - const result = await ConfigurationHandler.editKey(setting, updatedConfig[setting]); - - // Update the app configuration frontend-cached setting in memory with the new value - const wzConfig = new WazuhConfig(); - wzConfig.setConfig({ ...wzConfig.getConfig(), ...{ [setting]: formatValueCachedConfiguration(updatedConfig[setting]) } }); - - // Show restart and/or reload message in toast - const response = result.data.data; - response.needRestart && restartToast(); - response.needReload && reloadToast(); - response.needHealtCheck && executeHealtCheck(); - } catch (error) { - return Promise.reject(error); - } -} - -const reloadToast = () => { - getToasts().add({ - color: 'success', - title: 'This settings require you to reload the page to take effect.', - text: - - window.location.reload()} size="s">Reload page - - - }) -} - -const executeHealtCheck = () => { - const toast = getToasts().add({ - color: 'warning', - title: 'You must execute the health check for the changes to take effect', - toastLifeTimeMs: 5000, - text: - - - { - getToasts().remove(toast); - window.location.href = '#/health-check'; - }} size="s">Execute health check - - - }); -} - -const restartToast = () => { - getToasts().add({ - color: 'warning', - title: `You must restart ${PLUGIN_PLATFORM_NAME} for the changes to take effect`, - }); -} - -const successToast = () => { - getToasts().add({ - color: 'success', - title: 'The configuration has been successfully updated', - }); -} - -const formatValueCachedConfiguration = (value) => typeof value === 'string' - ? isNaN(Number(value)) ? value : Number(value) - : value; +); diff --git a/public/components/settings/configuration/components/categories/categories.tsx b/public/components/settings/configuration/components/categories/categories.tsx deleted file mode 100644 index b32d8d94ea..0000000000 --- a/public/components/settings/configuration/components/categories/categories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Wazuh app - React component building the configuration component. - * - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ -import React, { } from 'react'; -import { Category } from './components'; -import { ISetting } from '../../configuration'; -import { EuiFlexGroup } from '@elastic/eui'; - -interface ICategoriesProps { - config: ISetting[], - updatedConfig: {[field:string]: string | number | boolean | []} - setUpdatedConfig({}): void -} - -export const Categories:React.FunctionComponent = ({ config, updatedConfig, setUpdatedConfig }) => { - const categories: {[category:string]: ISetting[]} = config.reduce((acc, conf) => { - if (!conf.category) return acc; - return { - ...acc, - [conf.category]: [ - ...(acc[conf.category] || []), - conf, - ] - } - }, {}) - return ( - - {Object.keys(categories).map((category, idx) => ( - ))} - - ); -} diff --git a/public/components/settings/configuration/components/categories/components/category/category.tsx b/public/components/settings/configuration/components/categories/components/category/category.tsx index cee948b173..cf4327f007 100644 --- a/public/components/settings/configuration/components/categories/components/category/category.tsx +++ b/public/components/settings/configuration/components/categories/components/category/category.tsx @@ -12,8 +12,6 @@ */ import React, { } from 'react'; -import { FieldForm } from './components'; -import { ISetting } from '../../../../configuration'; import { EuiFlexItem, EuiPanel, @@ -25,15 +23,29 @@ import { EuiFormRow } from '@elastic/eui'; import { EuiIconTip } from '@elastic/eui'; +import { EpluginSettingType, TPluginSettingWithKey, UI_LOGGER_LEVELS } from '../../../../../../../../common/constants'; +import { getPluginSettingDescription } from '../../../../../../../../common/services/settings'; +import classNames from 'classnames'; +import { InputForm } from '../../../../../../common/form'; +import { getErrorOrchestrator } from '../../../../../../../react-services/common-services'; +import { UI_ERROR_SEVERITIES } from '../../../../../../../react-services/error-orchestrator/types'; +import { updateAppConfig } from '../../../../../../../redux/actions/appConfigActions'; +import { WzRequest } from '../../../../../../../react-services'; +import { WzButtonModalConfirm } from '../../../../../../common/buttons'; +import { useDispatch } from 'react-redux'; +import { getHttp } from '../../../../../../../kibana-services'; +import { getAssetURL } from '../../../../../../../utils/assets'; + interface ICategoryProps { - name: string - items: ISetting[] - updatedConfig: { [field: string]: string | number | boolean | [] } - setUpdatedConfig({ }): void + title: string + items: TPluginSettingWithKey[] + currentConfiguration: { [field: string]: any } + changedConfiguration: { [field: string]: any } + onChangeFieldForm: () => void } -export const Category: React.FunctionComponent = ({ name, items, updatedConfig, setUpdatedConfig }) => { +export const Category: React.FunctionComponent = ({ title, items, currentConfiguration, changedConfiguration, onChangeFieldForm }) => { return ( @@ -45,33 +57,44 @@ export const Category: React.FunctionComponent = ({ name, items, - {items.map((item, idx) => ( - - - {item.name} - {isUpdated(updatedConfig, item) - && { + const isUpdated = changedConfiguration?.[item.key] && !changedConfiguration?.[item.key]?.error; + return ( + + + {item.title} + {isUpdated && ( + } - } - description={item.description} > - - - - - ))} + type='dot' + color='warning' + aria-label={item.key} + content='Unsaved' /> + )} + + } + description={item.description} > + + + ) + })} ) -} - -const isUpdated = (configs, item) => typeof configs[item.setting] !== 'undefined' \ No newline at end of file +}; diff --git a/public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap b/public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap deleted file mode 100644 index 55482f7924..0000000000 --- a/public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FieldForm component renders correctly to match the snapshot 1`] = ` - - - - -
-
- - - - -
-
-
-
-
-
-`; diff --git a/public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx b/public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx deleted file mode 100644 index e551966749..0000000000 --- a/public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Wazuh app - React test for FieldForm component. - * - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - * - */ - -import React from 'react'; -import { FieldForm } from './field-form'; -import { ISetting } from '../../../../../configuration'; -import { mount } from 'enzyme'; - -describe('FieldForm component', () => { - it('renders correctly to match the snapshot', () => { - const item: ISetting = { - setting: 'string', - value: 'boolean', - description: 'string', - category: 'string', - name: 'string', - form: { type: 'text', params: {} } - }; - const updatedConfig = {}; - const setUpdatedConfig = jest.fn(); - - const wrapper = mount( - - ); - - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/public/components/settings/configuration/components/categories/components/category/components/field-form.tsx b/public/components/settings/configuration/components/categories/components/category/components/field-form.tsx deleted file mode 100644 index 54abed4317..0000000000 --- a/public/components/settings/configuration/components/categories/components/category/components/field-form.tsx +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Wazuh app - React component building the configuration component. - * - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ -import React, { useState, useEffect } from 'react'; -import { validate } from 'node-cron'; -import { - EuiFieldText, - EuiFieldNumber, - EuiSwitch, - EuiSelect, - EuiCodeEditor, - EuiTextColor -} from '@elastic/eui'; -import { ISetting } from '../../../../../configuration'; -import 'brace/mode/javascript'; -import 'brace/snippets/javascript'; -import 'brace/ext/language_tools'; -import "brace/ext/searchbox"; -import { - UI_ERROR_SEVERITIES, - UIErrorLog, UIErrorSeverity, - UILogLevel, -} from '../../../../../../../../react-services/error-orchestrator/types'; -import { UI_LOGGER_LEVELS } from '../../../../../../../../../common/constants'; -import { getErrorOrchestrator } from '../../../../../../../../react-services/common-services'; -import _ from 'lodash'; - -interface IFieldForm { - item: ISetting - updatedConfig: { [field: string]: string | number | boolean | [] } - setUpdatedConfig({ }): void -} -export const FieldForm: React.FunctionComponent = (props) => { - const { item } = props; - switch (item.form.type) { - case 'text': - return - case 'number': - return - case 'boolean': - return - case 'list': - return - case 'array': - return - case 'interval': - return - default: - return null; - } -}; - -//#region forms -const TextForm: React.FunctionComponent = (props) => ( - onChange(e.target.value, props)} />); - -const NumberForm: React.FunctionComponent = (props) => ( - onChange(e.target.value, props)} />) -const BooleanForm: React.FunctionComponent = (props) => ( - onChange(e.target.checked, props)} />); - -const ListForm: React.FunctionComponent = (props) => ( - onChange(e.target.value, props)} />); - -const IntervalForm: React.FunctionComponent = (props) => { - const [interval, setInterval] = useState(getValue(props)); - const [invalid, setInvalid] = useState(false); - useEffect(() => { - if (validate(interval)) { - setInvalid(false); - getValue(props) !== interval && onChange(interval, props); - } else { - setInvalid(true); - deleteChange(props); - } - }, [interval]) - return ( - <> - setInterval(e.target.value)} - /> - {invalid && Invalid cron schedule expressions} - - ); -} - -const ArrayForm: React.FunctionComponent = (props) => { - const [list, setList] = useState(JSON.stringify(getValue(props))); - - useEffect(() => { - checkErrors(); - }, [list]); - - const checkErrors = () => { - try { - const parsed = JSON.parse(list); - onChange(parsed, props); - } catch (error) { - const options: UIErrorLog = { - context: `${FieldForm.name}.checkErrors`, - level: UI_LOGGER_LEVELS.ERROR as UILogLevel, - severity: UI_ERROR_SEVERITIES.UI as UIErrorSeverity, - error: { - error: error, - message: error.message || error, - title: error.message || error, - }, - }; - - getErrorOrchestrator().handleError(options); - } - } - return ( - - ); -} - -//#endregion - -//#region Helpers - -const getValue = ({ item, updatedConfig }: IFieldForm) => typeof updatedConfig[item.setting] !== 'undefined' - ? updatedConfig[item.setting] - : item.value; - -const onChange = (value: string | number | boolean | [], props: IFieldForm) => { - const { updatedConfig, setUpdatedConfig, item } = props; - if(!_.isEqual(item.value,value)){ - setUpdatedConfig({ - ...updatedConfig, - [item.setting]: value, - }) - }else{ - deleteChange(props); - } -} - -const deleteChange = (props: IFieldForm) => { - const { updatedConfig, setUpdatedConfig, item } = props; - const newConfig = { ...updatedConfig }; - delete newConfig[item.setting]; - setUpdatedConfig(newConfig); -} - -//#endregion diff --git a/public/components/settings/configuration/components/categories/components/category/components/index.ts b/public/components/settings/configuration/components/categories/components/category/components/index.ts deleted file mode 100644 index f8dd9704e0..0000000000 --- a/public/components/settings/configuration/components/categories/components/category/components/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Wazuh app - React component building the configuration component. - * - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -export { FieldForm } from './field-form'; \ No newline at end of file diff --git a/public/components/settings/configuration/components/categories/index.ts b/public/components/settings/configuration/components/categories/index.ts deleted file mode 100644 index f665665238..0000000000 --- a/public/components/settings/configuration/components/categories/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Wazuh app - React component building the configuration component. - * - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ -export { Categories } from './categories'; \ No newline at end of file diff --git a/public/components/settings/configuration/components/header.tsx b/public/components/settings/configuration/components/header.tsx index baee512ec3..7bc47fb1d3 100644 --- a/public/components/settings/configuration/components/header.tsx +++ b/public/components/settings/configuration/components/header.tsx @@ -11,8 +11,7 @@ * Find more information about this on the LICENSE file. */ -import React, { useState, useEffect, Fragment } from 'react'; -import {categoriesNames} from '../../../../../common/config-equivalences'; +import React, { useState, useEffect } from 'react'; import { AppNavigate } from '../../../../react-services/app-navigate'; import { EuiFlexGroup, @@ -28,7 +27,7 @@ import { PLUGIN_PLATFORM_WAZUH_DOCUMENTATION_URL_PATH_APP_CONFIGURATION } from ' import { getPluginDataPath } from '../../../../../common/plugin'; import { webDocumentationLink } from '../../../../../common/services/web_documentation'; -export const Header = ({query, setQuery}) => { +export const Header = ({query, setQuery, searchBarFilters}) => { return ( @@ -39,13 +38,12 @@ export const Header = ({query, setQuery}) => { - + ) -} - +}; const Title = () => { return ( @@ -68,7 +66,7 @@ const Title = () => { ) -} +}; const SubTitle = () => { return ( @@ -78,16 +76,15 @@ const SubTitle = () => { ) -} +}; -const SearchBar = ({query, setQuery}) => { - const [categories, setCategories] = useState([]); +const SearchBar = ({query, setQuery, searchBarFilters}) => { const [error, setError] = useState(); + useEffect(() => { - const cats = categoriesNames.map(item => ({value: item})); - setCategories(cats); getDefaultCategory(setQuery) - }, []) + }, []); + const onChange = (args) => { if(args.error){ setError(args.error); @@ -95,28 +92,23 @@ const SearchBar = ({query, setQuery}) => { setError(undefined); setQuery(args); } - } + }; + return ( - + <> {!!error && {`${error.name}: ${error.message}`} } - + ) -} +}; const getDefaultCategory = (setQuery) => { const category:string | undefined = AppNavigate.getUrlParameter('category') category && setQuery(`category:(${category})`) -} +}; diff --git a/public/components/settings/configuration/components/index.ts b/public/components/settings/configuration/components/index.ts index 4b36cdf37d..cbc25aaeb7 100644 --- a/public/components/settings/configuration/components/index.ts +++ b/public/components/settings/configuration/components/index.ts @@ -11,5 +11,4 @@ * Find more information about this on the LICENSE file. */ export { Header } from './header'; -export { Categories } from './categories'; export { BottomBar } from './bottom-bar'; \ No newline at end of file diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index 780af7476f..ff995bd03e 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -12,71 +12,237 @@ */ import React, { useState, useEffect } from 'react'; -import { Header, Categories, BottomBar } from './components'; +import { useDispatch, useSelector } from 'react-redux'; +import { Header, BottomBar } from './components'; import { useKbnLoadingIndicator} from '../../common/hooks'; +import { useFormChanged } from '../../common/form/hooks'; import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, EuiPage, EuiPageBody, EuiPageHeader, EuiSpacer, Query, } from '@elastic/eui'; -import { - configEquivalences, - nameEquivalence, - categoriesEquivalence, - formEquivalence -} from '../../../../common/config-equivalences'; import store from '../../../redux/store' import { updateSelectedSettingsSection } from '../../../redux/actions/appStateActions'; -import { withUserAuthorizationPrompt, withErrorBoundary, withReduxProvider } from '../../common/hocs' -import { WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../../common/constants'; +import { withUserAuthorizationPrompt, withErrorBoundary, withReduxProvider } from '../../common/hocs'; +import { EpluginSettingType, PLUGIN_PLATFORM_NAME, PLUGIN_SETTINGS, PLUGIN_SETTINGS_CATEGORIES, UI_LOGGER_LEVELS, WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../../common/constants'; import { compose } from 'redux'; +import { formatSettingValueFromForm, getSettingsDefaultList } from '../../../../common/services/settings'; +import _ from 'lodash'; +import { Category } from './components/categories/components'; +import { WzRequest } from '../../../react-services'; +import { UIErrorLog, UIErrorSeverity, UILogLevel, UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { getToasts } from '../../../kibana-services'; +import path from 'path'; +import { updateAppConfig } from '../../../redux/actions/appConfigActions'; export type ISetting = { - setting: string + key: string value: boolean | string | number | object description: string category: string name: string readonly?: boolean form: { type: string, params: {} } -} +}; + +const pluginSettingConfigurableUI = getSettingsDefaultList() + .filter(categorySetting => categorySetting.configurableUI) + .map(setting => ({ ...setting, category: PLUGIN_SETTINGS_CATEGORIES[setting.category].title})); + +const settingsCategoriesSearchBarFilters = [...new Set(pluginSettingConfigurableUI.map(({category}) => category))].sort().map(category => ({value: category})) + +const transformToSettingsByCategories = (settings) => { + const settingsSortedByCategories = settings.reduce((accum, pluginSettingConfiguration) => ({ + ...accum, + [pluginSettingConfiguration.category]: [ + ...(accum[pluginSettingConfiguration.category] || []), + {...pluginSettingConfiguration} + ] + }),{}) + return Object.entries(settingsSortedByCategories).map(([category, categorySettings]) => { + return { + category, + settings: categorySettings + } + }).filter(categoryEntry => categoryEntry.settings.length); +}; const WzConfigurationSettingsProvider = (props) => { const [loading, setLoading ] = useKbnLoadingIndicator(); - const [config, setConfig] = useState([]); const [query, setQuery] = useState(''); - const [updatedConfig, setUpdateConfig] = useState({}); + const [settingsByCategories, setSettingsByCategories] = useState(transformToSettingsByCategories(pluginSettingConfigurableUI)); + const currentConfiguration = useSelector(state => state.appConfig.data); + + const { + onChangeFormField, + fieldsCount, + fieldsErrorCount, + fields: changedConfiguration, + onFormFieldReset } = useFormChanged(); + const dispatch = useDispatch(); + useEffect(() => { store.dispatch(updateSelectedSettingsSection('configuration')); - const rawConfig = props.wazuhConfig.getConfig(); - const formatedConfig = Object.keys(rawConfig).reduce((acc, conf) => [ - ...acc, - { - setting: conf, - value: rawConfig[conf], - description: configEquivalences[conf], - category: categoriesEquivalence[conf], - name: nameEquivalence[conf], - form: formEquivalence[conf], - } - ], []); - setConfig(formatedConfig); }, []); + + const onChangeSearchQuery = (query) => { + setQuery(query); + const search = Query.execute(query.query || query, pluginSettingConfigurableUI, ['description', 'key', 'title']); + + if(search){ + const result = transformToSettingsByCategories(search); + setSettingsByCategories(result); + }; + } + + // https://github.com/elastic/eui/blob/aa4cfd7b7c34c2d724405a3ecffde7fe6cf3b50f/src/components/search_bar/query/query.ts#L138-L163 + // const search = Query.execute(query.query || query, pluginSettingConfigurableUI, ['description', 'key', 'title']); + // let settingsByCategories = []; + + // if(search){ + // const settingsSortedByCategories = search.reduce((accum, pluginSettingConfiguration) => ({ + // ...accum, + // [pluginSettingConfiguration.category]: [ + // ...(accum[pluginSettingConfiguration.category] || []), + // {...pluginSettingConfiguration} + // ] + // }),{}) + // settingsByCategories = Object.entries(settingsSortedByCategories).map(([category, categorySettings]) => { + // return { + // category, + // settings: categorySettings + // } + // }).filter(categoryEntry => categoryEntry.settings.length); + // }; + + const onSave = async () => { + setLoading(true); + try { + const settingsToUpdate = Object.entries(changedConfiguration).reduce((accum, [pluginSettingKey, {currentValue}]) => { + if(PLUGIN_SETTINGS[pluginSettingKey].configurableFile && PLUGIN_SETTINGS[pluginSettingKey].type === EpluginSettingType.filepicker){ + accum.fileUpload = { + ...accum.fileUpload, + [pluginSettingKey]: { + file: currentValue, + extension: path.extname(currentValue.name) + } + } + }else if(PLUGIN_SETTINGS[pluginSettingKey].configurableFile){ + accum.saveOnConfigurationFile = { + ...accum.saveOnConfigurationFile, + [pluginSettingKey]: formatSettingValueFromForm(pluginSettingKey, currentValue) + } + }; + return accum; + }, {saveOnConfigurationFile: {}, fileUpload: {}}); + + const requests = []; + + if(Object.keys(settingsToUpdate.saveOnConfigurationFile).length){ + requests.push(WzRequest.genericReq( + 'PUT', '/utils/configuration', + settingsToUpdate.saveOnConfigurationFile + )); + }; + + if(Object.keys(settingsToUpdate.fileUpload).length){ + requests.push(...Object.entries(settingsToUpdate.fileUpload) + .map(([pluginSettingKey, {file, extension}]) => { + // Create the form data + const formData = new FormData(); + formData.append('file', file); + formData.append('extension', extension); + return WzRequest.genericReq( + 'PUT', `/utils/configuration/files/${pluginSettingKey}`, + formData, + {overwriteHeaders: {'content-type': 'multipart/form-data'}} + ) + })); + }; + + const responses = await Promise.all(requests); + + // Show the toasts if necessary + responses.some(({data: { data: {requireRestart}}}) => requireRestart) && toastRequireRestart(); + responses.some(({data: { data: {requireReload}}}) => requireReload) && toastRequireReload(); + responses.some(({data: { data: {requireHealtCheck}}}) => requireHealtCheck) && toastRequireHealthcheckExecution(); + + // Update the app configuration frontend-cached setting in memory with the new values + dispatch(updateAppConfig({ + ...responses.reduce((accum, {data: {data}}) => { + return { + ...accum, + ...(data.updatedConfiguration ? {...data.updatedConfiguration} : {}), + } + },{}) + })); + + // Show the success toast + successToast(); + + // Reset the form changed configuration + onFormFieldReset(); + } catch (error) { + const options: UIErrorLog = { + context: `${WzConfigurationSettingsProvider.name}.onSave`, + level: UI_LOGGER_LEVELS.ERROR as UILogLevel, + severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity, + store: true, + error: { + error: error, + message: error.message || error, + title: `Error saving the configuration: ${error.message || error}`, + }, + }; + + getErrorOrchestrator().handleError(options); + } finally { + setLoading(false); + }; + }; + return ( -
+
- + + {settingsByCategories && settingsByCategories.map(({category, settings}) => ( + + ) + )} + - + {fieldsCount > 0 && ( + + )} ); @@ -86,3 +252,46 @@ export const WzConfigurationSettings = compose ( withReduxProvider, withUserAuthorizationPrompt(null, [WAZUH_ROLE_ADMINISTRATOR_NAME]) )(WzConfigurationSettingsProvider); + +const toastRequireReload = () => { + getToasts().add({ + color: 'success', + title: 'This setting require you to reload the page to take effect.', + text: + + window.location.reload()} size="s">Reload page + + + }); +}; + +const toastRequireHealthcheckExecution = () => { + const toast = getToasts().add({ + color: 'warning', + title: 'You must execute the health check for the changes to take effect', + toastLifeTimeMs: 5000, + text: + + + { + getToasts().remove(toast); + window.location.href = '#/health-check'; + }} size="s">Execute health check + + + }); +}; + +const toastRequireRestart = () => { + getToasts().add({ + color: 'warning', + title: `You must restart ${PLUGIN_PLATFORM_NAME} for the changes to take effect`, + }); +}; + +const successToast = () => { + getToasts().add({ + color: 'success', + title: 'The configuration has been successfully updated', + }); +}; diff --git a/public/components/settings/configuration/utils/configuration-handler.js b/public/components/settings/configuration/utils/configuration-handler.js deleted file mode 100644 index 8c15295277..0000000000 --- a/public/components/settings/configuration/utils/configuration-handler.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Wazuh app - Configuration handler service - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import { WzRequest } from '../../../../react-services/wz-request'; -import { AppState } from '../../../../react-services/app-state'; - -export default class ConfigurationHandler { - /** - * Set the configuration key - */ - static async editKey(key, value) { - try { - if (key === 'ip.selector') { - AppState.setPatternSelector(value); - } - const result = await WzRequest.genericReq('PUT', '/utils/configuration', { - key, - value - }); - return result; - } catch (error) { - return Promise.reject(error); - } - } -} diff --git a/server/lib/initial-wazuh-config.ts b/server/lib/initial-wazuh-config.ts index e485443f5a..af17cf00b5 100644 --- a/server/lib/initial-wazuh-config.ts +++ b/server/lib/initial-wazuh-config.ts @@ -15,7 +15,6 @@ import { PLUGIN_SETTINGS_CATEGORIES, } from '../../common/constants'; import { webDocumentationLink } from '../../common/services/web_documentation'; -import { configEquivalences } from '../../common/config-equivalences'; const header: string = `--- # From 599941935c1c4cc2b0d3d9bd7186a632cfcdd1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 09:33:49 +0200 Subject: [PATCH 06/44] feat(settings): support updating multiple setting at the same time Changed the endpoint that updating the plugin setting to support multiple settings at the same time Refactor the getConfiguration service. Split the logic to: - Read the file and transform to JSON - Obfuscate the password key of the host configuration --- .../settings/configuration/configuration.tsx | 28 +----- server/controllers/wazuh-utils/wazuh-utils.ts | 87 ++++++++++++------- server/lib/get-configuration.ts | 57 +++++++++--- server/lib/update-configuration.ts | 29 +++---- server/routes/wazuh-utils/wazuh-utils.ts | 5 +- 5 files changed, 115 insertions(+), 91 deletions(-) diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index ff995bd03e..e81318df14 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -125,22 +125,14 @@ const WzConfigurationSettingsProvider = (props) => { setLoading(true); try { const settingsToUpdate = Object.entries(changedConfiguration).reduce((accum, [pluginSettingKey, {currentValue}]) => { - if(PLUGIN_SETTINGS[pluginSettingKey].configurableFile && PLUGIN_SETTINGS[pluginSettingKey].type === EpluginSettingType.filepicker){ - accum.fileUpload = { - ...accum.fileUpload, - [pluginSettingKey]: { - file: currentValue, - extension: path.extname(currentValue.name) - } - } - }else if(PLUGIN_SETTINGS[pluginSettingKey].configurableFile){ + if(PLUGIN_SETTINGS[pluginSettingKey].configurableFile){ accum.saveOnConfigurationFile = { ...accum.saveOnConfigurationFile, [pluginSettingKey]: formatSettingValueFromForm(pluginSettingKey, currentValue) } }; return accum; - }, {saveOnConfigurationFile: {}, fileUpload: {}}); + }, {saveOnConfigurationFile: {}}); const requests = []; @@ -150,22 +142,6 @@ const WzConfigurationSettingsProvider = (props) => { settingsToUpdate.saveOnConfigurationFile )); }; - - if(Object.keys(settingsToUpdate.fileUpload).length){ - requests.push(...Object.entries(settingsToUpdate.fileUpload) - .map(([pluginSettingKey, {file, extension}]) => { - // Create the form data - const formData = new FormData(); - formData.append('file', file); - formData.append('extension', extension); - return WzRequest.genericReq( - 'PUT', `/utils/configuration/files/${pluginSettingKey}`, - formData, - {overwriteHeaders: {'content-type': 'multipart/form-data'}} - ) - })); - }; - const responses = await Promise.all(requests); // Show the toasts if necessary diff --git a/server/controllers/wazuh-utils/wazuh-utils.ts b/server/controllers/wazuh-utils/wazuh-utils.ts index 9a33c3c649..7bd876da24 100644 --- a/server/controllers/wazuh-utils/wazuh-utils.ts +++ b/server/controllers/wazuh-utils/wazuh-utils.ts @@ -16,7 +16,7 @@ import { getConfiguration } from '../../lib/get-configuration'; import { read } from 'read-last-lines'; import { UpdateConfigurationFile } from '../../lib/update-configuration'; import jwtDecode from 'jwt-decode'; -import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_DATA_LOGS_RAW_PATH, WAZUH_UI_LOGS_RAW_PATH } from '../../../common/constants'; +import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_DATA_LOGS_RAW_PATH, PLUGIN_SETTINGS } from '../../../common/constants'; import { ManageHosts } from '../../lib/manage-hosts'; import { KibanaRequest, RequestHandlerContext, KibanaResponseFactory } from 'src/core/server'; import { getCookieValueByName } from '../../lib/cookie'; @@ -62,41 +62,35 @@ export class WazuhUtilsCtrl { * @param {Object} response * @returns {Object} Configuration File or ErrorResponse */ - async updateConfigurationFile(context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) { - try { - // Check if user has administrator role in token - const token = getCookieValueByName(request.headers.cookie,'wz-token'); - if(!token){ - return ErrorResponse('No token provided', 401, 401, response); - }; - const decodedToken = jwtDecode(token); - if(!decodedToken){ - return ErrorResponse('No permissions in token', 401, 401, response); - }; - if(!decodedToken.rbac_roles || !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID)){ - return ErrorResponse('No administrator role', 401, 401, response); - };response - // Check the provided token is valid - const apiHostID = getCookieValueByName(request.headers.cookie,'wz-api'); - if( !apiHostID ){ - return ErrorResponse('No API id provided', 401, 401, response); - }; - const responseTokenIsWorking = await context.wazuh.api.client.asCurrentUser.request('GET', '/', {}, {apiHostID}); - if(responseTokenIsWorking.status !== 200){ - return ErrorResponse('Token is not valid', 401, 401, response); + updateConfigurationFile = this.routeDecoratorProtectedAdministratorRoleValidToken( + async (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => { + + let requireHealthCheck: boolean = false, + requireReload: boolean = false, + requireRestart: boolean = false; + + // Plugin settings configurables in the configuration file. + const pluginSettingsConfigurableFile = Object.keys(request.body) + .filter(pluginSettingKey => PLUGIN_SETTINGS[pluginSettingKey].configurableFile) + .reduce((accum, pluginSettingKey: string) => ({...accum, [pluginSettingKey]: request.body[pluginSettingKey]}), {}); + + if(Object.keys(pluginSettingsConfigurableFile).length){ + // Update the configuration file. + await updateConfigurationFile.updateConfiguration(pluginSettingsConfigurableFile); + + requireHealthCheck = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requireHealthCheck)) || requireHealthCheck; + requireReload = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requireReload)) || requireReload; + requireRestart = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requireRestart)) || requireRestart; }; - const result = await updateConfigurationFile.updateConfiguration(request); + return response.ok({ body: { - statusCode: 200, - error: 0, - data: result + data: { requireHealthCheck, requireReload, requireRestart, updatedConfiguration: request.body } } }); - } catch (error) { - return ErrorResponse(error.message || error, 3021, 500, response); - } - } + }, + 3021 + ) /** * Returns Wazuh app logs @@ -127,5 +121,34 @@ export class WazuhUtilsCtrl { } } - + private routeDecoratorProtectedAdministratorRoleValidToken(routeHandler, errorCode: number){ + return async (context, request, response) => { + try{ + // Check if user has administrator role in token + const token = getCookieValueByName(request.headers.cookie,'wz-token'); + if(!token){ + return ErrorResponse('No token provided', 401, 401, response); + }; + const decodedToken = jwtDecode(token); + if(!decodedToken){ + return ErrorResponse('No permissions in token', 401, 401, response); + }; + if(!decodedToken.rbac_roles || !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID)){ + return ErrorResponse('No administrator role', 401, 401, response); + }; + // Check the provided token is valid + const apiHostID = getCookieValueByName(request.headers.cookie,'wz-api'); + if( !apiHostID ){ + return ErrorResponse('No API id provided', 401, 401, response); + }; + const responseTokenIsWorking = await context.wazuh.api.client.asCurrentUser.request('GET', '/', {}, {apiHostID}); + if(responseTokenIsWorking.status !== 200){ + return ErrorResponse('Token is not valid', 401, 401, response); + }; + return await routeHandler(context, request, response) + }catch(error){ + return ErrorResponse(error.message || error, errorCode, 500, response); + } + } + } } diff --git a/server/lib/get-configuration.ts b/server/lib/get-configuration.ts index 8e6d2d5d2a..c3a35e15f5 100644 --- a/server/lib/get-configuration.ts +++ b/server/lib/get-configuration.ts @@ -11,29 +11,60 @@ */ import fs from 'fs'; import yml from 'js-yaml'; -import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_CACHE_TIME } from '../../common/constants'; +import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_CACHE_TIME, PLUGIN_SETTINGS, EpluginSettingType } from '../../common/constants'; let cachedConfiguration: any = null; let lastAssign: number = new Date().getTime(); -export function getConfiguration(isUpdating: boolean = false) { +/** + * Get the plugin configuration and cache it. + * @param options.force Force to read the configuration and no use the cache . + * @returns plugin configuration in JSON + */ +export function getConfiguration(options: {force?: boolean} = {}) { try { const now = new Date().getTime(); const dateDiffer = now - lastAssign; - if (!cachedConfiguration || dateDiffer >= WAZUH_CONFIGURATION_CACHE_TIME || isUpdating) { - const raw = fs.readFileSync(WAZUH_DATA_CONFIG_APP_PATH, { encoding: 'utf-8' }); - const file = yml.load(raw); + if (!cachedConfiguration || dateDiffer >= WAZUH_CONFIGURATION_CACHE_TIME || options?.force) { + cachedConfiguration = obfuscateHostsConfiguration( + readPluginConfigurationFile(WAZUH_DATA_CONFIG_APP_PATH), + ['password'] + ); - for (const host of file.hosts) { - Object.keys(host).forEach((k) => { - host[k].password = '*****'; - }); - } - cachedConfiguration = { ...file }; lastAssign = now; } return cachedConfiguration; } catch (error) { return false; - } -} + }; +}; + +/** + * Read the configuration file and transform to JSON. + * @param path File path of the plugin configuration file. + * @returns Configuration as JSON. + */ + function readPluginConfigurationFile(filepath: string) { + const content = fs.readFileSync(filepath, { encoding: 'utf-8' }); + return yml.load(content); +}; + +/** + * Obfuscate fields of the hosts configuration. + * @param configuration Plugin configuration as JSON. + * @param obfuscateHostConfigurationKeys Keys to obfuscate its value in the hosts configuration. + * @returns + */ +function obfuscateHostsConfiguration(configuration: any, obfuscateHostConfigurationKeys: string[]){ + configuration.hosts = Object.entries(configuration.hosts) + .reduce((accum, [hostID, hostConfiguration]) => { + return {...accum, [hostID]: { + ...hostConfiguration, + ...(obfuscateHostConfigurationKeys + .reduce((accumObfuscateHostConfigurationKeys, obfuscateHostConfigurationKey) => + ({...accumObfuscateHostConfigurationKeys, [obfuscateHostConfigurationKey]: '*****'}), {}) + ) + }} + }, {}) + return configuration; +}; diff --git a/server/lib/update-configuration.ts b/server/lib/update-configuration.ts index 67bb9be148..e6a82a4560 100644 --- a/server/lib/update-configuration.ts +++ b/server/lib/update-configuration.ts @@ -12,7 +12,8 @@ import fs from 'fs'; import { log } from './logger'; import { getConfiguration } from './get-configuration'; -import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_SETTINGS_NEED_RESTART, WAZUH_CONFIGURATION_SETTINGS_NEED_RELOAD, WAZUH_CONFIGURATION_SETTINGS_NEED_HEALTH_CHECK } from '../../common/constants'; +import { WAZUH_DATA_CONFIG_APP_PATH } from '../../common/constants'; +import { formatSettingValueToFile } from '../../common/services/settings'; export class UpdateConfigurationFile { constructor() { @@ -30,7 +31,7 @@ export class UpdateConfigurationFile { try { const data = fs.readFileSync(this.file, { encoding: 'utf-8' }); const re = new RegExp(`^${key}\\s{0,}:\\s{1,}.*`, 'gm'); - const formatedValue = this.formatValue(value); + const formatedValue = formatSettingValueToFile(value); const result = exists ? data.replace(re, `${key}: ${formatedValue}`) : `${data}\n${key}: ${formatedValue}`; @@ -43,33 +44,29 @@ export class UpdateConfigurationFile { } } - formatValue = (value) => typeof value === 'string' - ? isNaN(Number(value)) ? `'${value}'` : value - : typeof value === 'object' - ? JSON.stringify(value) - : value - formatValueCachedConfiguration = (value) => typeof value === 'string' ? isNaN(Number(value)) ? value : Number(value) : value; /** * Updates wazuh.yml file. If it fails, it throws the error to the next function. - * @param {Object} input + * @param {Object} updatedConfiguration */ - updateConfiguration(input) { + updateConfiguration(updatedConfiguration) { try { if (this.busy) { throw new Error('Another process is updating the configuration file'); } this.busy = true; - const configuration = getConfiguration(true) || {}; - - const { key, value } = (input || {}).body || {}; - this.updateLine(key, value, typeof configuration[key] !== 'undefined'); + const pluginConfiguration = getConfiguration({force: true}) || {}; - // Update the app configuration server-cached setting in memory with the new value - configuration[key] = this.formatValueCachedConfiguration(value); + for(const pluginSettingKey in updatedConfiguration){ + // Store the configuration in the configuration file. + const value = updatedConfiguration[pluginSettingKey]; + this.updateLine(pluginSettingKey, value, typeof pluginConfiguration[pluginSettingKey] !== 'undefined'); + // Update the app configuration server-cached setting in memory with the new value. + pluginConfiguration[pluginSettingKey] = value; + }; this.busy = false; log( diff --git a/server/routes/wazuh-utils/wazuh-utils.ts b/server/routes/wazuh-utils/wazuh-utils.ts index 30ba0b0992..d1a31b4377 100644 --- a/server/routes/wazuh-utils/wazuh-utils.ts +++ b/server/routes/wazuh-utils/wazuh-utils.ts @@ -30,10 +30,7 @@ export function WazuhUtilsRoutes(router: IRouter) { { path: '/utils/configuration', validate: { - body: schema.object({ - key: schema.string(), - value: schema.any() - }) + body: schema.any() } }, async (context, request, response) => ctrl.updateConfigurationFile(context, request, response) From 9b977e6c8ee491cd873d519d55c4413930bbce76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 10:38:16 +0200 Subject: [PATCH 07/44] clean: remove not used code --- public/components/common/form/index.tsx | 2 +- .../configuration/components/bottom-bar.tsx | 20 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx index 2dd286dba4..dec089463b 100644 --- a/public/components/common/form/index.tsx +++ b/public/components/common/form/index.tsx @@ -32,7 +32,7 @@ export const InputForm = (props: IInputForm) => { value={value} onChange={onChange} isInvalid={isInvalid} - field={{...field, ...(field?.validate ? {validate: field.validate.bind(field)} : {})}} + field={field} /> ); diff --git a/public/components/settings/configuration/components/bottom-bar.tsx b/public/components/settings/configuration/components/bottom-bar.tsx index 34cabaf53e..06e9c4672a 100644 --- a/public/components/settings/configuration/components/bottom-bar.tsx +++ b/public/components/settings/configuration/components/bottom-bar.tsx @@ -12,10 +12,7 @@ */ import React from 'react'; -import ConfigurationHandler from '../utils/configuration-handler'; -//@ts-ignore -import { getToasts } from '../../../../kibana-services'; -import { ISetting } from '../configuration' + import { EuiBottomBar, EuiFlexGroup, @@ -24,15 +21,6 @@ import { EuiButtonEmpty, EuiButton } from '@elastic/eui'; -import { WazuhConfig } from '../../../../react-services/wazuh-config'; -import { UI_LOGGER_LEVELS, PLUGIN_PLATFORM_NAME } from '../../../../../common/constants'; -import { - UI_ERROR_SEVERITIES, - UIErrorLog, - UIErrorSeverity, - UILogLevel, -} from '../../../../react-services/error-orchestrator/types'; -import { getErrorOrchestrator } from '../../../../react-services/common-services'; interface IBottomBarProps { totalCount: number @@ -41,12 +29,12 @@ interface IBottomBarProps { onSave: () => void } -export const BottomBar: React.FunctionComponent = ({ totalCount, errorsCount, onCancel, onSave }) => ( +export const BottomBar: React.FunctionComponent = ({ totalCount, onCancel, onSave }) => ( - + - + ); From 17ad2e8f58621e96a2cea05fc99e24ade5900296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 12:37:31 +0200 Subject: [PATCH 08/44] fix: fixed category name in `Settings/Configuration` --- .../components/categories/components/category/category.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/components/settings/configuration/components/categories/components/category/category.tsx b/public/components/settings/configuration/components/categories/components/category/category.tsx index cf4327f007..b7029c72d6 100644 --- a/public/components/settings/configuration/components/categories/components/category/category.tsx +++ b/public/components/settings/configuration/components/categories/components/category/category.tsx @@ -52,7 +52,7 @@ export const Category: React.FunctionComponent = ({ title, items -

{name}

+

{title}

From a454dd8348900082de28a494abecc559c721fa6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 13:34:10 +0200 Subject: [PATCH 09/44] fix(settings): fixed error due to missing service --- common/services/settings.ts | 18 +++++++++++++++++- server/lib/update-configuration.ts | 8 -------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/common/services/settings.ts b/common/services/settings.ts index d508c8ecf7..460a542a26 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -50,4 +50,20 @@ const formatSettingValueFromFormType = { [EpluginSettingType.switch]: (value: string): boolean => Boolean(value), [EpluginSettingType.editor]: (value: any): any => value, // Array form transforms the value. It is coming a valid JSON. [EpluginSettingType.select]: (value: any): any => value, -}; \ No newline at end of file +}; + +/** + * Format the plugin setting value received in the backend to store in the plugin configuration file (.yml). + * @param value plugin setting value sent to the endpoint + * @returns valid value to .yml + */ + export function formatSettingValueToFile(value: any) { + const formatter = formatSettingValueToFileType[typeof value] || formatSettingValueToFileType.default; + return formatter(value); +}; + +const formatSettingValueToFileType = { + string: (value: string): string => `"${value.replace(/"/,'\\"').replace(/\n/g,'\\n')}"`, // Escape the " character and new line + object: (value: any): string => JSON.stringify(value), + default: (value: any): any => value +}; diff --git a/server/lib/update-configuration.ts b/server/lib/update-configuration.ts index e6a82a4560..0c9adb84d2 100644 --- a/server/lib/update-configuration.ts +++ b/server/lib/update-configuration.ts @@ -44,9 +44,6 @@ export class UpdateConfigurationFile { } } - formatValueCachedConfiguration = (value) => typeof value === 'string' - ? isNaN(Number(value)) ? value : Number(value) - : value; /** * Updates wazuh.yml file. If it fails, it throws the error to the next function. * @param {Object} updatedConfiguration @@ -74,11 +71,6 @@ export class UpdateConfigurationFile { 'Updating configuration', 'debug' ); - return { - needRestart: WAZUH_CONFIGURATION_SETTINGS_NEED_RESTART.includes(key), - needReload: WAZUH_CONFIGURATION_SETTINGS_NEED_RELOAD.includes(key), - needHealtCheck: WAZUH_CONFIGURATION_SETTINGS_NEED_HEALTH_CHECK.includes(key) - }; } catch (error) { log('update-configuration:updateConfiguration', error.message || error); this.busy = false; From 5d6861554feab6ccc7b13737a21e276bba3a84fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Fri, 16 Sep 2022 14:16:23 +0200 Subject: [PATCH 10/44] fix(settings): refactor the form and inputs of `Settings/Configuration` to control the global state of the form --- public/components/common/form/hooks.tsx | 110 ++++++++++-------- public/components/common/form/index.tsx | 13 +-- .../components/common/form/input_editor.tsx | 4 +- .../components/common/form/input_select.tsx | 4 +- .../components/common/form/input_switch.tsx | 4 +- .../configuration/components/bottom-bar.tsx | 6 +- .../components/category/category.tsx | 11 +- .../settings/configuration/configuration.tsx | 84 ++++++------- 8 files changed, 113 insertions(+), 123 deletions(-) diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx index 4fafe6512d..03bf4bb704 100644 --- a/public/components/common/form/hooks.tsx +++ b/public/components/common/form/hooks.tsx @@ -6,65 +6,81 @@ function getValueFromEvent(event, type){ return getValueFromEventType?.[type]?.(event) || getValueFromEventType.default(event) }; -type TuseFormFieldChanged = { - onChange?: any - transformUIInputValue?: (inputValue: any) => any - type: string - validate?: (currentValue: any) => string | undefined -} const getValueFromEventType = { [EpluginSettingType.switch] : (event: any) => event.target.checked, [EpluginSettingType.editor]: (value: any) => value, default: (event: any) => event.target.value, -} +}; -export const useFormFieldChanged = (field, initialValue: any, { onChange: onChangeFormField, transformUIInputValue, type, validate }: TuseFormFieldChanged) => { - const [value, setValue] = useState(initialValue); - const [validationError, setValidationError] = useState(null); - function onChange(event){ - const inputValue = getValueFromEvent(event, type); - const currentValue = transformUIInputValue ? transformUIInputValue(inputValue) : inputValue; - const error = validate ? validate(currentValue) : false; - setValue(currentValue); - validationError !== error && setValidationError(error); - onChangeFormField && onChangeFormField({field, changed: !_.isEqual(initialValue, currentValue), previousValue: value, currentValue, error}); - }; +export const useForm = (fields) => { + const [formFields, setFormFields] = useState(Object.entries(fields).reduce((accum, [fieldKey, fieldConfiguration]) => ({ + ...accum, + [fieldKey]: { + currentValue: fieldConfiguration.initialValue, + initialValue: fieldConfiguration.initialValue, + } + }), {})); - function resetValue(){ - setValue(initialValue); - setValidationError(null); - }; + const enhanceFields = Object.entries(formFields).filter(f => {return f}).reduce((accum, [fieldKey, fieldState]) => ({ + ...accum, + [fieldKey]: { + ...fields[fieldKey], + type: fields[fieldKey].type, + value: fieldState.currentValue, + changed: !_.isEqual(fieldState.initialValue, fieldState.currentValue), + error: fields[fieldKey]?.validate?.(fieldState.currentValue), + onChange: (event) => { + const inputValue = getValueFromEvent(event, fields[fieldKey].type); + const currentValue = fields[fieldKey]?.transformUIInputValue?.(inputValue) ?? inputValue; + setFormFields(state => ({ + ...state, + [fieldKey]: { + ...state[fieldKey], + currentValue, + } + })) + }, + } + }), {}); - return { value, error: validationError, onChange, resetValue }; -}; + const changed = Object.fromEntries( + Object.entries(enhanceFields).filter(([, {changed}]) => changed).map(([fieldKey, {value}]) => ([fieldKey, value])) + ); -export const useFormChanged = () => { - const [formFieldsChanged, setFormFieldsChanged] = useState({}); + const errors = Object.fromEntries( + Object.entries(enhanceFields).filter(([, {error}]) => error).map(([fieldKey, {error}]) => ([fieldKey, error])) + ); - function onChangeFormField({ field, changed, currentValue, error }: { field: string, changed: boolean, currentValue: any, error: any}){ - if(changed){ - setFormFieldsChanged(state => ({...state, [field]: {currentValue, error}})); - }else{ - onFormFieldReset(field); - }; + function undoneChanges(){ + setFormFields(state => Object.fromEntries( + Object.entries(state).map(([fieldKey, fieldConfiguration]) => ([ + fieldKey, + { + ...fieldConfiguration, + currentValue: fieldConfiguration.initialValue + } + ])) + )); }; - const fieldsCount: number = Object.keys(formFieldsChanged).length; - const fieldsSuccessCount: number = Object.entries(formFieldsChanged).filter(([_, {error}]) => !error).length; - const fieldsErrorCount: number = fieldsCount - fieldsSuccessCount; - const isValid = fieldsCount === fieldsSuccessCount; - - function onFormFieldReset(field?: string){ - if(field){ - setFormFieldsChanged(state => { - const { [field]: _, ...rest } = state; - return {...rest}; - }); - }else{ - setFormFieldsChanged({}); - }; + function doneChanges(){ + setFormFields(state => Object.fromEntries( + Object.entries(state).map(([fieldKey, fieldConfiguration]) => ([ + fieldKey, + { + ...fieldConfiguration, + initialValue: fieldConfiguration.currentValue + } + ])) + )); }; - return { onChangeFormField, fieldsCount, fieldsSuccessCount, fieldsErrorCount, fields: formFieldsChanged, onFormFieldReset, isValid }; + return { + fields: enhanceFields, + changed, + errors, + undoneChanges, + doneChanges + }; }; diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx index dec089463b..5fab2224f9 100644 --- a/public/components/common/form/index.tsx +++ b/public/components/common/form/index.tsx @@ -10,15 +10,9 @@ import { EuiFormRow, } from '@elastic/eui'; -export const InputForm = (props: IInputForm) => { - const { field, label = null, initialValue, onChange: onChangeInputForm, preInput = null, postInput = null } = props; - const { value, error, onChange } = useFormFieldChanged( - field.key, - initialValue, - { validate: field?.validate, onChange: onChangeInputForm, type: field.type, transformUIInputValue: field?.transformUIInputValue } - ); +export const InputForm = ({ type, value, onChange, error, label, preInput, postInput, ...rest}) => { - const ComponentInput = Input[field.type]; + const ComponentInput = Input[type]; if(!ComponentInput){ return null; @@ -28,11 +22,10 @@ export const InputForm = (props: IInputForm) => { const input = ( ); diff --git a/public/components/common/form/input_editor.tsx b/public/components/common/form/input_editor.tsx index 2d8f29892d..41002090ca 100644 --- a/public/components/common/form/input_editor.tsx +++ b/public/components/common/form/input_editor.tsx @@ -4,10 +4,10 @@ import { } from '@elastic/eui'; import { IInputFormType } from './types'; -export const InputFormEditor = ({field, value, onChange}: IInputFormType) => { +export const InputFormEditor = ({options, value, onChange}: IInputFormType) => { return ( { +export const InputFormSelect = ({ options, value, onChange }: IInputFormType) => { return ( diff --git a/public/components/common/form/input_switch.tsx b/public/components/common/form/input_switch.tsx index b668e71aec..e6c88b7a65 100644 --- a/public/components/common/form/input_switch.tsx +++ b/public/components/common/form/input_switch.tsx @@ -4,10 +4,10 @@ import { } from '@elastic/eui'; import { IInputFormType } from './types'; -export const InputFormSwitch = ({ field, value, onChange }: IInputFormType) => { +export const InputFormSwitch = ({ options, value, onChange, ...rest }: IInputFormType) => { return ( diff --git a/public/components/settings/configuration/components/bottom-bar.tsx b/public/components/settings/configuration/components/bottom-bar.tsx index 06e9c4672a..bfb44f269f 100644 --- a/public/components/settings/configuration/components/bottom-bar.tsx +++ b/public/components/settings/configuration/components/bottom-bar.tsx @@ -23,16 +23,16 @@ import { } from '@elastic/eui'; interface IBottomBarProps { - totalCount: number + unsavedCount: number errorsCount: number onCancel: () => void onSave: () => void } -export const BottomBar: React.FunctionComponent = ({ totalCount, onCancel, onSave }) => ( +export const BottomBar: React.FunctionComponent = ({ unsavedCount, onCancel, onSave }) => ( - + diff --git a/public/components/settings/configuration/components/categories/components/category/category.tsx b/public/components/settings/configuration/components/categories/components/category/category.tsx index b7029c72d6..07006374ac 100644 --- a/public/components/settings/configuration/components/categories/components/category/category.tsx +++ b/public/components/settings/configuration/components/categories/components/category/category.tsx @@ -45,7 +45,7 @@ interface ICategoryProps { onChangeFieldForm: () => void } -export const Category: React.FunctionComponent = ({ title, items, currentConfiguration, changedConfiguration, onChangeFieldForm }) => { +export const Category: React.FunctionComponent = ({ title, items }) => { return ( @@ -58,7 +58,7 @@ export const Category: React.FunctionComponent = ({ title, items {items.map((item, idx) => { - const isUpdated = changedConfiguration?.[item.key] && !changedConfiguration?.[item.key]?.error; + const isUpdated = item.changed && !item.error; return ( = ({ title, items } description={item.description} > ) diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index e81318df14..be7ed92655 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -15,7 +15,7 @@ import React, { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Header, BottomBar } from './components'; import { useKbnLoadingIndicator} from '../../common/hooks'; -import { useFormChanged } from '../../common/form/hooks'; +import { useForm } from '../../common/form/hooks'; import { EuiButton, EuiFlexGroup, @@ -31,14 +31,13 @@ import { updateSelectedSettingsSection } from '../../../redux/actions/appStateAc import { withUserAuthorizationPrompt, withErrorBoundary, withReduxProvider } from '../../common/hocs'; import { EpluginSettingType, PLUGIN_PLATFORM_NAME, PLUGIN_SETTINGS, PLUGIN_SETTINGS_CATEGORIES, UI_LOGGER_LEVELS, WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../../common/constants'; import { compose } from 'redux'; -import { formatSettingValueFromForm, getSettingsDefaultList } from '../../../../common/services/settings'; +import { formatSettingValueFromForm, getSettingDefaultValue, getSettingsDefaultList } from '../../../../common/services/settings'; import _ from 'lodash'; import { Category } from './components/categories/components'; import { WzRequest } from '../../../react-services'; import { UIErrorLog, UIErrorSeverity, UILogLevel, UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; import { getToasts } from '../../../kibana-services'; -import path from 'path'; import { updateAppConfig } from '../../../redux/actions/appConfigActions'; export type ISetting = { @@ -65,26 +64,25 @@ const transformToSettingsByCategories = (settings) => { {...pluginSettingConfiguration} ] }),{}) - return Object.entries(settingsSortedByCategories).map(([category, categorySettings]) => { - return { - category, - settings: categorySettings - } - }).filter(categoryEntry => categoryEntry.settings.length); + return Object.entries(settingsSortedByCategories) + .map(([category, settings]) => ({ category,settings })) + .filter(categoryEntry => categoryEntry.settings.length); }; +const pluginSettingsConfigurableUI = configuration => Object.fromEntries( + getSettingsDefaultList() + .filter(pluginSetting => pluginSetting.configurableUI) + .map(({key, type, validate, default: initialValue, transformUIInputValue}) => ([key, {type, validate, transformUIInputValue, initialValue: type === EpluginSettingType.editor ? JSON.stringify(configuration?.[key] ?? initialValue) : (configuration?.[key] ?? initialValue) }])) +); + + const WzConfigurationSettingsProvider = (props) => { const [loading, setLoading ] = useKbnLoadingIndicator(); const [query, setQuery] = useState(''); - const [settingsByCategories, setSettingsByCategories] = useState(transformToSettingsByCategories(pluginSettingConfigurableUI)); + // const [settingsByCategories, setSettingsByCategories] = useState(transformToSettingsByCategories(pluginSettingConfigurableUI)); const currentConfiguration = useSelector(state => state.appConfig.data); - const { - onChangeFormField, - fieldsCount, - fieldsErrorCount, - fields: changedConfiguration, - onFormFieldReset } = useFormChanged(); + const { fields, changed, errors, doneChanges, undoneChanges } = useForm(pluginSettingsConfigurableUI(currentConfiguration)); const dispatch = useDispatch(); useEffect(() => { @@ -93,38 +91,28 @@ const WzConfigurationSettingsProvider = (props) => { const onChangeSearchQuery = (query) => { setQuery(query); - const search = Query.execute(query.query || query, pluginSettingConfigurableUI, ['description', 'key', 'title']); + }; - if(search){ - const result = transformToSettingsByCategories(search); - setSettingsByCategories(result); - }; - } + const visibleSettings = Object.entries(fields) + .map(([fieldKey, fieldForm]) => ({ + ...fieldForm, + key: fieldKey, + category: PLUGIN_SETTINGS_CATEGORIES[PLUGIN_SETTINGS[fieldKey].category].title, + type: PLUGIN_SETTINGS[fieldKey].type, + options: PLUGIN_SETTINGS[fieldKey]?.options, + title: PLUGIN_SETTINGS[fieldKey]?.title, + description: PLUGIN_SETTINGS[fieldKey]?.description + })); // https://github.com/elastic/eui/blob/aa4cfd7b7c34c2d724405a3ecffde7fe6cf3b50f/src/components/search_bar/query/query.ts#L138-L163 - // const search = Query.execute(query.query || query, pluginSettingConfigurableUI, ['description', 'key', 'title']); - // let settingsByCategories = []; + const search = Query.execute(query.query || query, visibleSettings, ['description', 'key', 'title']); - // if(search){ - // const settingsSortedByCategories = search.reduce((accum, pluginSettingConfiguration) => ({ - // ...accum, - // [pluginSettingConfiguration.category]: [ - // ...(accum[pluginSettingConfiguration.category] || []), - // {...pluginSettingConfiguration} - // ] - // }),{}) - // settingsByCategories = Object.entries(settingsSortedByCategories).map(([category, categorySettings]) => { - // return { - // category, - // settings: categorySettings - // } - // }).filter(categoryEntry => categoryEntry.settings.length); - // }; + const visibleCategories = transformToSettingsByCategories(search || visibleSettings); const onSave = async () => { setLoading(true); try { - const settingsToUpdate = Object.entries(changedConfiguration).reduce((accum, [pluginSettingKey, {currentValue}]) => { + const settingsToUpdate = Object.entries(changed).reduce((accum, [pluginSettingKey, currentValue]) => { if(PLUGIN_SETTINGS[pluginSettingKey].configurableFile){ accum.saveOnConfigurationFile = { ...accum.saveOnConfigurationFile, @@ -163,7 +151,7 @@ const WzConfigurationSettingsProvider = (props) => { successToast(); // Reset the form changed configuration - onFormFieldReset(); + doneChanges(); } catch (error) { const options: UIErrorLog = { context: `${WzConfigurationSettingsProvider.name}.onSave`, @@ -199,23 +187,21 @@ const WzConfigurationSettingsProvider = (props) => { }]}/> - {settingsByCategories && settingsByCategories.map(({category, settings}) => ( + {visibleCategories && visibleCategories.map(({category, settings}) => ( + /> ) )} - {fieldsCount > 0 && ( + {Object.keys(changed).length > 0 && ( )} From cf15ef8f8a173519b0c3deb63b1b03bbdb0682f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 19 Sep 2022 10:01:35 +0200 Subject: [PATCH 11/44] fix: add value transformation for the form inputs and output of fields changed --- common/constants.ts | 34 ++++++++++++++++++- public/components/common/form/hooks.tsx | 4 +-- public/components/common/form/index.tsx | 2 +- .../components/common/form/input_switch.tsx | 7 ++-- public/components/common/form/types.ts | 1 + .../settings/configuration/configuration.tsx | 20 ++++++++++- 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index b328735eba..ca16c8edc5 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -417,7 +417,9 @@ export type TpluginSetting = { requireReload?: boolean requireRestart?: boolean options?: TpluginSettingOptionsChoices | TpluginSettingOptionsNumber | TpluginSettingOptionsEditor | TpluginSettingOptionsSwitch - transformUIInputValue?: (value: boolean | string) => boolean + toUIInput?: (value: any) => any + transformUIInputValue?: (value: any) => any + toUIOutput?: (value: any) => any }; export type TPluginSettingWithKey = TpluginSetting & { key: string }; @@ -651,6 +653,16 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, + toUIInput: function(value : any): any{ + return JSON.stringify(value); + }, + toUIOutput: function(value: string): any{ + try{ + return JSON.parse(value); + }catch(error){ + return value; + }; + }, }, "cron.statistics.index.creation": { title: "Index creation", @@ -803,6 +815,16 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, + toUIInput: function(value : any): any{ + return JSON.stringify(value); + }, + toUIOutput: function(value: string): any{ + try{ + return JSON.parse(value); + }catch(error){ + return value; + }; + }, }, "enrollment.dns": { title: "Enrollment DNS", @@ -1136,6 +1158,16 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, + toUIInput: function(value : any): any{ + return JSON.stringify(value); + }, + toUIOutput: function(value: string): any{ + try{ + return JSON.parse(value); + }catch(error){ + return value; + }; + }, }, "ip.selector": { title: "IP selector", diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx index 03bf4bb704..f700df739d 100644 --- a/public/components/common/form/hooks.tsx +++ b/public/components/common/form/hooks.tsx @@ -32,7 +32,7 @@ export const useForm = (fields) => { error: fields[fieldKey]?.validate?.(fieldState.currentValue), onChange: (event) => { const inputValue = getValueFromEvent(event, fields[fieldKey].type); - const currentValue = fields[fieldKey]?.transformUIInputValue?.(inputValue) ?? inputValue; + const currentValue = fields[fieldKey]?.transformInputValue?.(inputValue) ?? inputValue; setFormFields(state => ({ ...state, [fieldKey]: { @@ -45,7 +45,7 @@ export const useForm = (fields) => { }), {}); const changed = Object.fromEntries( - Object.entries(enhanceFields).filter(([, {changed}]) => changed).map(([fieldKey, {value}]) => ([fieldKey, value])) + Object.entries(enhanceFields).filter(([, {changed}]) => changed).map(([fieldKey, {value}]) => ([fieldKey, fields[fieldKey]?.transformOutputValue?.(value) ?? value])) ); const errors = Object.fromEntries( diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx index 5fab2224f9..0681a4c6b6 100644 --- a/public/components/common/form/index.tsx +++ b/public/components/common/form/index.tsx @@ -5,7 +5,7 @@ import { InputFormNumber } from './input_number'; import { InputFormText } from './input_text'; import { InputFormSwitch } from './input_switch'; import { InputFormSelect } from './input_select'; -import { useFormFieldChanged } from './hooks'; + import { EuiFormRow, } from '@elastic/eui'; diff --git a/public/components/common/form/input_switch.tsx b/public/components/common/form/input_switch.tsx index e6c88b7a65..6292405298 100644 --- a/public/components/common/form/input_switch.tsx +++ b/public/components/common/form/input_switch.tsx @@ -4,11 +4,14 @@ import { } from '@elastic/eui'; import { IInputFormType } from './types'; -export const InputFormSwitch = ({ options, value, onChange, ...rest }: IInputFormType) => { +export const InputFormSwitch = ({ options, value, onChange }: IInputFormType) => { + const checked = Object.entries(options.switch.values) + .find(([, { value: statusValue }]) => value === statusValue)[0]; + return ( ); diff --git a/public/components/common/form/types.ts b/public/components/common/form/types.ts index 16b5f25d4f..b1d8c51120 100644 --- a/public/components/common/form/types.ts +++ b/public/components/common/form/types.ts @@ -5,6 +5,7 @@ export interface IInputFormType { value: any onChange: (event: any) => void isInvalid?: boolean + options: any }; export interface IInputForm { diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index be7ed92655..3806d4e866 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -72,7 +72,25 @@ const transformToSettingsByCategories = (settings) => { const pluginSettingsConfigurableUI = configuration => Object.fromEntries( getSettingsDefaultList() .filter(pluginSetting => pluginSetting.configurableUI) - .map(({key, type, validate, default: initialValue, transformUIInputValue}) => ([key, {type, validate, transformUIInputValue, initialValue: type === EpluginSettingType.editor ? JSON.stringify(configuration?.[key] ?? initialValue) : (configuration?.[key] ?? initialValue) }])) + .map(({ + key, + type, + validate, + default: initialValue, + transformUIInputValue, + toUIInput, + toUIOutput, + ...rest + }) => ([ + key, + { + type, + validate: validate?.bind?.(rest), + transformInputValue: transformUIInputValue?.bind?.(rest), + transformOutputValue: toUIOutput?.bind?.(rest), + initialValue: toUIInput ? toUIInput.bind(rest)(configuration?.[key] ?? initialValue) : (configuration?.[key] ?? initialValue) + } + ])) ); From 0a1ee1d2b57570866b18847d549698571658d055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 19 Sep 2022 15:59:27 +0200 Subject: [PATCH 12/44] fix(settings): renamed properties related to transform the value of the input --- common/constants.ts | 70 +++++++++---------- public/components/common/form/hooks.tsx | 4 +- .../settings/configuration/configuration.tsx | 12 ++-- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index ca16c8edc5..efac533612 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -417,9 +417,9 @@ export type TpluginSetting = { requireReload?: boolean requireRestart?: boolean options?: TpluginSettingOptionsChoices | TpluginSettingOptionsNumber | TpluginSettingOptionsEditor | TpluginSettingOptionsSwitch - toUIInput?: (value: any) => any - transformUIInputValue?: (value: any) => any - toUIOutput?: (value: any) => any + uiFormTransformChangedInputValue?: (value: any) => any + uiFormTransformInputValueToConfigurationValue?: (value: any) => any + uiFormTransformConfigurationValueToInputValue?: (value: any) => any }; export type TPluginSettingWithKey = TpluginSetting & { key: string }; @@ -487,7 +487,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -507,7 +507,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -527,7 +527,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } }, }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -547,7 +547,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -567,7 +567,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -587,7 +587,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -607,7 +607,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -627,7 +627,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -653,10 +653,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, - toUIInput: function(value : any): any{ + uiFormTransformConfigurationValueToInputValue: function(value : any): any{ return JSON.stringify(value); }, - toUIOutput: function(value: string): any{ + uiFormTransformInputValueToConfigurationValue: function(value: string): any{ try{ return JSON.parse(value); }catch(error){ @@ -760,7 +760,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -815,10 +815,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, - toUIInput: function(value : any): any{ + uiFormTransformConfigurationValueToInputValue: function(value : any): any{ return JSON.stringify(value); }, - toUIOutput: function(value: string): any{ + uiFormTransformInputValueToConfigurationValue: function(value: string): any{ try{ return JSON.parse(value); }catch(error){ @@ -860,7 +860,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -880,7 +880,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -900,7 +900,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -920,7 +920,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -940,7 +940,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -960,7 +960,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -980,7 +980,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1000,7 +1000,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1020,7 +1020,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1040,7 +1040,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1060,7 +1060,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1080,7 +1080,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1100,7 +1100,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1120,7 +1120,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1141,7 +1141,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1158,10 +1158,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, - toUIInput: function(value : any): any{ + uiFormTransformConfigurationValueToInputValue: function(value : any): any{ return JSON.stringify(value); }, - toUIOutput: function(value: string): any{ + uiFormTransformInputValueToConfigurationValue: function(value: string): any{ try{ return JSON.parse(value); }catch(error){ @@ -1185,7 +1185,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, @@ -1282,7 +1282,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { } } }, - transformUIInputValue: function(value: boolean | string): boolean{ + uiFormTransformChangedInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, }, diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx index f700df739d..d19c4964ee 100644 --- a/public/components/common/form/hooks.tsx +++ b/public/components/common/form/hooks.tsx @@ -32,7 +32,7 @@ export const useForm = (fields) => { error: fields[fieldKey]?.validate?.(fieldState.currentValue), onChange: (event) => { const inputValue = getValueFromEvent(event, fields[fieldKey].type); - const currentValue = fields[fieldKey]?.transformInputValue?.(inputValue) ?? inputValue; + const currentValue = fields[fieldKey]?.transformChangedInputValue?.(inputValue) ?? inputValue; setFormFields(state => ({ ...state, [fieldKey]: { @@ -45,7 +45,7 @@ export const useForm = (fields) => { }), {}); const changed = Object.fromEntries( - Object.entries(enhanceFields).filter(([, {changed}]) => changed).map(([fieldKey, {value}]) => ([fieldKey, fields[fieldKey]?.transformOutputValue?.(value) ?? value])) + Object.entries(enhanceFields).filter(([, {changed}]) => changed).map(([fieldKey, {value}]) => ([fieldKey, fields[fieldKey]?.transformChangedOutputValue?.(value) ?? value])) ); const errors = Object.fromEntries( diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index 3806d4e866..a1e9ca84b2 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -77,18 +77,18 @@ const pluginSettingsConfigurableUI = configuration => Object.fromEntries( type, validate, default: initialValue, - transformUIInputValue, - toUIInput, - toUIOutput, + uiFormTransformChangedInputValue, + uiFormTransformConfigurationValueToInputValue, + uiFormTransformInputValueToConfigurationValue, ...rest }) => ([ key, { type, validate: validate?.bind?.(rest), - transformInputValue: transformUIInputValue?.bind?.(rest), - transformOutputValue: toUIOutput?.bind?.(rest), - initialValue: toUIInput ? toUIInput.bind(rest)(configuration?.[key] ?? initialValue) : (configuration?.[key] ?? initialValue) + transformChangedInputValue: uiFormTransformChangedInputValue?.bind?.(rest), + transformChangedOutputValue: uiFormTransformInputValueToConfigurationValue?.bind?.(rest), + initialValue: uiFormTransformConfigurationValueToInputValue ? uiFormTransformConfigurationValueToInputValue.bind(rest)(configuration?.[key] ?? initialValue) : (configuration?.[key] ?? initialValue) } ])) ); From b4ef2cd9facce585533268ea1fe50dc1964e8d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 19 Sep 2022 17:19:00 +0200 Subject: [PATCH 13/44] feat(settings): add description to the plugin setting definition properties --- common/constants.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index efac533612..a5f3d1fc87 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -405,21 +405,21 @@ export enum EpluginSettingType{ }; export type TpluginSetting = { - title: string - description: string - category: SettingCategory - type: EpluginSettingType - default: any - defaultHidden?: any - configurableFile: boolean, - configurableUI: boolean - requireHealthCheck?: boolean - requireReload?: boolean - requireRestart?: boolean - options?: TpluginSettingOptionsChoices | TpluginSettingOptionsNumber | TpluginSettingOptionsEditor | TpluginSettingOptionsSwitch - uiFormTransformChangedInputValue?: (value: any) => any - uiFormTransformInputValueToConfigurationValue?: (value: any) => any - uiFormTransformConfigurationValueToInputValue?: (value: any) => any + title: string // Define the text displayed in the UI. + description: string // Description. + category: SettingCategory // Category. + type: EpluginSettingType // Type. + default: any // Default value. + defaultHidden?: any // Default value if it is not set. It has preference over `default` + configurableFile: boolean, // Configurable from the configuration file + configurableUI: boolean // Configurable from the UI (Settings/Configuration) + requireHealthCheck?: boolean // Modify the setting requires running the plugin health check (frontend) + requireReload?: boolean // Modify the setting requires reloading the browser tab (frontend) + requireRestart?: boolean // Modify the setting requires restarting the plugin platform to take effect + options?: TpluginSettingOptionsChoices | TpluginSettingOptionsNumber | TpluginSettingOptionsEditor | TpluginSettingOptionsSwitch // Define options related to the `type` + uiFormTransformChangedInputValue?: (value: any) => any // Transform the input value. The result is saved in the form global state of Settings/Configuration + uiFormTransformInputValueToConfigurationValue?: (value: any) => any // Transform the configuration value or default as initial value for the input in Settings/Configuration + uiFormTransformConfigurationValueToInputValue?: (value: any) => any // Transform the input value changed in the form of Settings/Configuration and returned in the `changed` property of the hook useForm }; export type TPluginSettingWithKey = TpluginSetting & { key: string }; From 138db83a9a51f4b966e533da2a7cde761c560bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 20 Sep 2022 09:03:01 +0200 Subject: [PATCH 14/44] fix(settings): fix getConfiguration service when the configuration file has no `hosts` entry --- server/lib/get-configuration.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/server/lib/get-configuration.ts b/server/lib/get-configuration.ts index c3a35e15f5..01edd1cf6d 100644 --- a/server/lib/get-configuration.ts +++ b/server/lib/get-configuration.ts @@ -56,15 +56,17 @@ export function getConfiguration(options: {force?: boolean} = {}) { * @returns */ function obfuscateHostsConfiguration(configuration: any, obfuscateHostConfigurationKeys: string[]){ - configuration.hosts = Object.entries(configuration.hosts) - .reduce((accum, [hostID, hostConfiguration]) => { - return {...accum, [hostID]: { - ...hostConfiguration, - ...(obfuscateHostConfigurationKeys - .reduce((accumObfuscateHostConfigurationKeys, obfuscateHostConfigurationKey) => - ({...accumObfuscateHostConfigurationKeys, [obfuscateHostConfigurationKey]: '*****'}), {}) - ) - }} - }, {}) + if(configuration.hosts){ + configuration.hosts = Object.entries(configuration.hosts) + .reduce((accum, [hostID, hostConfiguration]) => { + return {...accum, [hostID]: { + ...hostConfiguration, + ...(obfuscateHostConfigurationKeys + .reduce((accumObfuscateHostConfigurationKeys, obfuscateHostConfigurationKey) => + ({...accumObfuscateHostConfigurationKeys, [obfuscateHostConfigurationKey]: '*****'}), {}) + ) + }} + }, {}); + }; return configuration; }; From 710800dfde67bc573a6d0ed443f3da2aa0aaac76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 20 Sep 2022 15:47:35 +0200 Subject: [PATCH 15/44] fix(settings): Fixed error when do changes of the `useForm` hook an rename methods of this --- public/components/common/form/hooks.tsx | 18 +++++++++--------- .../settings/configuration/configuration.tsx | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx index d19c4964ee..8bebee8102 100644 --- a/public/components/common/form/hooks.tsx +++ b/public/components/common/form/hooks.tsx @@ -12,7 +12,6 @@ const getValueFromEventType = { default: (event: any) => event.target.value, }; - export const useForm = (fields) => { const [formFields, setFormFields] = useState(Object.entries(fields).reduce((accum, [fieldKey, fieldConfiguration]) => ({ ...accum, @@ -22,14 +21,15 @@ export const useForm = (fields) => { } }), {})); - const enhanceFields = Object.entries(formFields).filter(f => {return f}).reduce((accum, [fieldKey, fieldState]) => ({ + const enhanceFields = Object.entries(formFields).reduce((accum, [fieldKey, {currentValue: value, ...restFieldState}]) => ({ ...accum, [fieldKey]: { ...fields[fieldKey], + ...restFieldState, type: fields[fieldKey].type, - value: fieldState.currentValue, - changed: !_.isEqual(fieldState.initialValue, fieldState.currentValue), - error: fields[fieldKey]?.validate?.(fieldState.currentValue), + value, + changed: !_.isEqual(restFieldState.initialValue, value), + error: fields[fieldKey]?.validate?.(restFieldState.currentValue), onChange: (event) => { const inputValue = getValueFromEvent(event, fields[fieldKey].type); const currentValue = fields[fieldKey]?.transformChangedInputValue?.(inputValue) ?? inputValue; @@ -52,7 +52,7 @@ export const useForm = (fields) => { Object.entries(enhanceFields).filter(([, {error}]) => error).map(([fieldKey, {error}]) => ([fieldKey, error])) ); - function undoneChanges(){ + function undoChanges(){ setFormFields(state => Object.fromEntries( Object.entries(state).map(([fieldKey, fieldConfiguration]) => ([ fieldKey, @@ -64,7 +64,7 @@ export const useForm = (fields) => { )); }; - function doneChanges(){ + function doChanges(){ setFormFields(state => Object.fromEntries( Object.entries(state).map(([fieldKey, fieldConfiguration]) => ([ fieldKey, @@ -80,7 +80,7 @@ export const useForm = (fields) => { fields: enhanceFields, changed, errors, - undoneChanges, - doneChanges + undoChanges, + doChanges }; }; diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index a1e9ca84b2..7b4f79603f 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -100,7 +100,7 @@ const WzConfigurationSettingsProvider = (props) => { // const [settingsByCategories, setSettingsByCategories] = useState(transformToSettingsByCategories(pluginSettingConfigurableUI)); const currentConfiguration = useSelector(state => state.appConfig.data); - const { fields, changed, errors, doneChanges, undoneChanges } = useForm(pluginSettingsConfigurableUI(currentConfiguration)); + const { fields, changed, errors, doChanges, undoChanges } = useForm(pluginSettingsConfigurableUI(currentConfiguration)); const dispatch = useDispatch(); useEffect(() => { @@ -169,7 +169,7 @@ const WzConfigurationSettingsProvider = (props) => { successToast(); // Reset the form changed configuration - doneChanges(); + doChanges(); } catch (error) { const options: UIErrorLog = { context: `${WzConfigurationSettingsProvider.name}.onSave`, @@ -219,7 +219,7 @@ const WzConfigurationSettingsProvider = (props) => { )} From 7d7decc9a81556d612a99319d6fbb99fe13e3693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 20 Sep 2022 15:49:30 +0200 Subject: [PATCH 16/44] tests(settings): add test related to the plugin settings and its configuration from the UI --- .../form/__snapshots__/index.test.tsx.snap | 244 ++++++++++++++++++ public/components/common/form/hooks.test.tsx | 137 ++++++++++ public/components/common/form/index.test.tsx | 25 ++ server/routes/wazuh-utils/wazuh-utils.test.ts | 236 ++++++++++++----- 4 files changed, 573 insertions(+), 69 deletions(-) create mode 100644 public/components/common/form/__snapshots__/index.test.tsx.snap create mode 100644 public/components/common/form/hooks.test.tsx create mode 100644 public/components/common/form/index.test.tsx diff --git a/public/components/common/form/__snapshots__/index.test.tsx.snap b/public/components/common/form/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..b1b7d75836 --- /dev/null +++ b/public/components/common/form/__snapshots__/index.test.tsx.snap @@ -0,0 +1,244 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`[component] InputForm Renders correctly to match the snapshot: Input: editor 1`] = ` +
+
+
+

+ Press Enter to start editing. +

+

+ When you're done, press Escape to stop editing. +

+
+
+