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/79] 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/79] 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/79] 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/79] 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/79] 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/79] 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 9a2b9ba8705512213ba76f8cf7d96097518d68f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 10:23:15 +0200 Subject: [PATCH 07/79] feat: add validation to the plugin settings Create services to validate Add the validation to the plugin settings --- common/constants.ts | 225 ++++++++++++++++++++++++--- common/services/settings-validate.ts | 68 ++++++++ 2 files changed, 274 insertions(+), 19 deletions(-) create mode 100644 common/services/settings-validate.ts diff --git a/common/constants.ts b/common/constants.ts index b328735eba..12c95c50b8 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -11,6 +11,8 @@ */ import path from 'path'; import { version } from '../package.json'; +import { validate as validateNodeCronInterval } from 'node-cron'; +import { composeValidate, validateBooleanIs, validateJSONArrayOfStrings, validateLiteral, validateNumber, validateStringNoEmpty, validateStringNoSpaces } from './services/settings-validate'; // Plugin export const PLUGIN_VERSION = version; @@ -353,25 +355,6 @@ 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 @@ -418,6 +401,8 @@ export type TpluginSetting = { requireRestart?: boolean options?: TpluginSettingOptionsChoices | TpluginSettingOptionsNumber | TpluginSettingOptionsEditor | TpluginSettingOptionsSwitch transformUIInputValue?: (value: boolean | string) => boolean + validate?: (value: any) => string | undefined + validateBackend?: (schema: any) => any }; export type TPluginSettingWithKey = TpluginSetting & { key: string }; @@ -468,6 +453,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireHealthCheck: true, + validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validateBackend: function(schema){ + return schema.string({validate: this.validate}); + }, }, "checks.api": { title: "API connection", @@ -488,6 +477,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.fields": { title: "Known fields", @@ -508,6 +501,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.maxBuckets": { title: "Set max buckets to 200000", @@ -528,6 +525,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.metaFields": { title: "Remove meta fields", @@ -548,6 +549,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.pattern": { title: "Index pattern", @@ -568,6 +573,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.setup": { title: "API version", @@ -588,6 +597,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.template": { title: "Index template", @@ -608,6 +621,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "checks.timeFilter": { title: "Set time filter to 24h", @@ -628,6 +645,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "cron.prefix": { title: "Cron prefix", @@ -637,6 +658,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { default: WAZUH_STATISTICS_DEFAULT_PREFIX, configurableFile: true, configurableUI: true, + validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, }, "cron.statistics.apis": { title: "Includes APIs", @@ -651,6 +676,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, + validate: validateJSONArrayOfStrings, + validateBackend: function(schema){ + return schema.string(this.validate); + }, }, "cron.statistics.index.creation": { title: "Index creation", @@ -681,6 +710,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireHealthCheck: true, + validate: function (value){ + return validateLiteral(this.options.choices.map(({value}) => value))(value) + }, + validateBackend: function(schema){ + return schema.oneOf(this.options.choices.map(({value}) => schema.literal(value))); + }, }, "cron.statistics.index.name": { title: "Index name", @@ -691,6 +726,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireHealthCheck: true, + validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, }, "cron.statistics.index.replicas": { title: "Index replicas", @@ -706,6 +745,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { min: 0 } }, + validate: function(value){ + return validateNumber(this.options.number)(value) + }, + validateBackend: function(schema){ + return schema.number({...this.options.number}); + }, }, "cron.statistics.index.shards": { title: "Index shards", @@ -721,6 +766,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { min: 1 } }, + validate: function(value){ + return validateNumber(this.options.number)(value) + }, + validateBackend: function(schema){ + return schema.number({...this.options.number}); + }, }, "cron.statistics.interval": { title: "Interval", @@ -731,6 +782,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireRestart: true, + validate: (value: string) => validateNodeCronInterval(value) ? undefined : "Interval is not valid.", + validateBackend: function(schema){ + return schema.string({validate: this.validate}); + }, }, "cron.statistics.status": { title: "Status", @@ -751,6 +806,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "customization.logo.app": { title: "Logo App", @@ -803,6 +862,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, + validate: validateJSONArrayOfStrings, + validateBackend: function(schema){ + return schema.string({validate: this.validate}); + }, }, "enrollment.dns": { title: "Enrollment DNS", @@ -812,6 +875,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { default: "", configurableFile: true, configurableUI: true, + validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validateBackend: function(schema){ + return schema.string({minLenght: 1, validate: this.validate}); + }, }, "enrollment.password": { title: "Enrollment Password", @@ -821,6 +888,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { default: "", configurableFile: true, configurableUI: false, + validate: validateStringNoEmpty, + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, }, "extensions.audit": { title: "System auditing", @@ -841,6 +912,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.aws": { title: "Amazon AWS", @@ -861,6 +936,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.ciscat": { title: "CIS-CAT", @@ -881,6 +960,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.docker": { title: "Docker Listener", @@ -901,6 +984,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.gcp": { title: "Google Cloud platform", @@ -921,6 +1008,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.gdpr": { title: "GDPR", @@ -941,6 +1032,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.hipaa": { title: "Hipaa", @@ -961,6 +1056,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.mitre": { title: "MITRE ATT&CK", @@ -981,6 +1080,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.nist": { title: "NIST", @@ -1001,6 +1104,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.oscap": { title: "OSCAP", @@ -1021,6 +1128,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.osquery": { title: "Osquery", @@ -1041,6 +1152,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.pci": { title: "PCI DSS", @@ -1061,6 +1176,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.tsc": { title: "TSC", @@ -1081,6 +1200,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "extensions.virustotal": { title: "Virustotal", @@ -1101,6 +1224,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "hideManagerAlerts": { title: "Hide manager alerts", @@ -1122,6 +1249,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "ip.ignore": { title: "Index pattern ignore", @@ -1136,6 +1267,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { language: 'json' } }, + validate: validateJSONArrayOfStrings, + validateBackend: function(schema){ + return schema.string({validate: this.validate}) + } }, "ip.selector": { title: "IP selector", @@ -1156,6 +1291,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "logs.level": { title: "Log level", @@ -1178,6 +1317,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireRestart: true, + validate: function (value){ + return validateLiteral(this.options.choices.map(({value}) => value))(value) + }, + validateBackend: function(schema){ + return schema.oneOf(this.options.choices.map(({value}) => schema.literal(value))); + }, }, "pattern": { title: "Index pattern", @@ -1188,6 +1333,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireHealthCheck: true, + validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, }, "timeout": { title: "Request timeout", @@ -1202,6 +1351,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { min: 1500 } }, + validate: function(value){ + return validateNumber(this.options.number)(value); + }, + validateBackend: function(schema){ + return schema.number({validate: this.validate}); + }, }, "wazuh.monitoring.creation": { title: "Index creation", @@ -1232,6 +1387,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireHealthCheck: true, + validate: function (value){ + return validateLiteral(this.options.choices.map(({value}) => value))(value) + }, + validateBackend: function(schema){ + return schema.oneOf(this.options.choices.map(({value}) => schema.literal(value))); + }, }, "wazuh.monitoring.enabled": { title: "Status", @@ -1253,6 +1414,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { transformUIInputValue: function(value: boolean | string): boolean{ return Boolean(value); }, + validate: validateBooleanIs, + validateBackend: function(schema){ + return schema.boolean(); + }, }, "wazuh.monitoring.frequency": { title: "Frequency", @@ -1268,6 +1433,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { min: 60 } }, + validate: function(value){ + return validateNumber(this.options.number)(value); + }, + validateBackend: function(schema){ + return schema.number({validate: this.validate}); + }, }, "wazuh.monitoring.pattern": { title: "Index pattern", @@ -1278,6 +1449,10 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireHealthCheck: true, + validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, }, "wazuh.monitoring.replicas": { title: "Index replicas", @@ -1293,6 +1468,12 @@ export const PLUGIN_SETTINGS: TpluginSettings = { min: 0 } }, + validate: function(value){ + return validateNumber(this.options.number)(value); + }, + validateBackend: function(schema){ + return schema.number({validate: this.validate}); + }, }, "wazuh.monitoring.shards": { title: "Index shards", @@ -1308,5 +1489,11 @@ export const PLUGIN_SETTINGS: TpluginSettings = { min: 1 } }, + validate: function(value){ + return validateNumber(this.options.number)(value); + }, + validateBackend: function(schema){ + return schema.number({validate: this.validate}); + }, } }; diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts new file mode 100644 index 0000000000..df42e79cab --- /dev/null +++ b/common/services/settings-validate.ts @@ -0,0 +1,68 @@ +import path from 'path'; + +// Utils +export const composeValidate = (...functions) => value => { + for(const fn of functions){ + const result = fn(value); + if(typeof result === 'string' && result.length > 0){ + return result; + }; + }; +}; + +// String +export const validateStringNoSpaces = (value: string): string | undefined => /^\S*$/.test(value) ? undefined : "It can't contain spaces."; +export const validateStringNoEmpty = (value: string): string | undefined => { + if(typeof value === 'string'){ + if(value.length === 0){ + return "Value can not be empty" + }else{ + return undefined; + } + }; +}; +export const validateStringMultipleLines = (options: {min?: number, max?: number} = {}) => (value: number) => { + const lines = value.split(/\r\n|\r|\n/).length; + if(typeof options.min !== 'undefined' && lines < options.min){ + return `The string should have more or ${options.min} line/s.`; + }; + if(typeof options.max !== 'undefined' && lines > options.max){ + return `The string should have less or ${options.max} line/s.`; + }; +}; + +// Boolean +export const validateBooleanIs = (value: string): string | undefined => typeof value === 'boolean' ? undefined : "It should be a boolean. Allowed values: true or false."; + +// Number +export const validateNumber = (options: {min?: number, max?: number} = {}) => (value: number) => { + if(typeof options.min !== 'undefined' && value < options.min){ + return `Value should be greater or equal than ${options.min}.`; + }; + if(typeof options.max !== 'undefined' && value > options.max){ + return `Value should be lower or equal than ${options.min}.`; + }; +}; + +// Complex +export const validateJSONArrayOfStrings = (value: string) => { + let parsed; + // Try to parse the string as JSON + try{ + parsed = JSON.parse(value); + }catch(error){ + return "Value can't be parsed. There is some error."; + }; + + // Check the JSON is an array + if(!Array.isArray(parsed)){ + return 'Value is not a valid list.'; + }; + + // Check the items are strings + if(parsed.some(value => typeof value !== 'string')){ + return 'There is a value that is not a string.'; + }; +}; + +export const validateLiteral = (...literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}`; From f530d7aa35bbc4a0fd6a839c0c04839d0bee281c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 10:24:16 +0200 Subject: [PATCH 08/79] feat(validation): add validation to the `PUT /utils/configuration` endpoint --- server/routes/wazuh-utils/wazuh-utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/routes/wazuh-utils/wazuh-utils.ts b/server/routes/wazuh-utils/wazuh-utils.ts index d1a31b4377..79be6ee21d 100644 --- a/server/routes/wazuh-utils/wazuh-utils.ts +++ b/server/routes/wazuh-utils/wazuh-utils.ts @@ -12,6 +12,7 @@ import { WazuhUtilsCtrl } from '../../controllers'; import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; +import { EpluginSettingType, PLUGIN_SETTINGS } from '../../../common/constants'; export function WazuhUtilsRoutes(router: IRouter) { const ctrl = new WazuhUtilsCtrl(); @@ -30,7 +31,10 @@ export function WazuhUtilsRoutes(router: IRouter) { { path: '/utils/configuration', validate: { - body: schema.any() + body: schema.object(Object.entries(PLUGIN_SETTINGS).filter(([, {configurableFile}]) => configurableFile).reduce((accum, [pluginSettingKey, {validateBackend}]) => ({ + ...accum, + [pluginSettingKey]: schema.maybe(validateBackend ? (validateBackend)(schema) : schema.any()) + }), {})) } }, async (context, request, response) => ctrl.updateConfigurationFile(context, request, response) From b6a722005497cad19818ac3c61dd7d88b397111c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 10:25:17 +0200 Subject: [PATCH 09/79] feat(validation): add validation to the configuration form in `Settings/Configuration` --- .../configuration/components/bottom-bar.tsx | 12 +++++++++--- .../categories/components/category/category.tsx | 14 +++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/public/components/settings/configuration/components/bottom-bar.tsx b/public/components/settings/configuration/components/bottom-bar.tsx index 34cabaf53e..3c4bea2788 100644 --- a/public/components/settings/configuration/components/bottom-bar.tsx +++ b/public/components/settings/configuration/components/bottom-bar.tsx @@ -51,16 +51,21 @@ export const BottomBar: React.FunctionComponent = ({ totalCount ); -const SettingLabel = ({ count, }) => ( +const SettingLabel = ({ count, errors }) => ( {`${count} unsaved settings`} + {errors ? ( + + {`${errors} setting with ${errors === 1 ? 'error' : 'errors'}`} + + ) : null} ); -const CancelButton = ({ onClick }) => ( +const CancelButton = ({ onClick}) => ( ( ); -const SaveButton = ({ onClick }) => ( +const SaveButton = ({ onClick, isDisabled }) => ( 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..e23d387f96 100644 --- a/public/components/settings/configuration/components/categories/components/category/category.tsx +++ b/public/components/settings/configuration/components/categories/components/category/category.tsx @@ -59,17 +59,28 @@ export const Category: React.FunctionComponent = ({ title, items {items.map((item, idx) => { const isUpdated = changedConfiguration?.[item.key] && !changedConfiguration?.[item.key]?.error; + const error = changedConfiguration?.[item.key]?.error; + return ( {item.title} + {error && ( + + )} {isUpdated && ( = ({ title, items Date: Tue, 13 Sep 2022 10:34:32 +0200 Subject: [PATCH 10/79] feat(validatio): remove no used import --- server/routes/wazuh-utils/wazuh-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/wazuh-utils/wazuh-utils.ts b/server/routes/wazuh-utils/wazuh-utils.ts index 79be6ee21d..3e0b2c8567 100644 --- a/server/routes/wazuh-utils/wazuh-utils.ts +++ b/server/routes/wazuh-utils/wazuh-utils.ts @@ -12,7 +12,7 @@ import { WazuhUtilsCtrl } from '../../controllers'; import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { EpluginSettingType, PLUGIN_SETTINGS } from '../../../common/constants'; +import { PLUGIN_SETTINGS } from '../../../common/constants'; export function WazuhUtilsRoutes(router: IRouter) { const ctrl = new WazuhUtilsCtrl(); 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 11/79] 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 f3fc8c8cc02a56d0b1e0c47a3f1355ff5cdaa5e3 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Tue, 13 Sep 2022 12:22:27 +0200 Subject: [PATCH 12/79] Add report header-footer configuration --- common/constants.ts | 32 +++++++++++++++++-- common/services/settings-validate.ts | 1 + public/components/common/form/index.tsx | 2 ++ .../common/form/input_text_area.tsx | 16 ++++++++++ server/lib/reporting/printer.ts | 19 +++++++---- 5 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 public/components/common/form/input_text_area.tsx diff --git a/common/constants.ts b/common/constants.ts index 12c95c50b8..5639b6b2a9 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -12,7 +12,7 @@ import path from 'path'; import { version } from '../package.json'; import { validate as validateNodeCronInterval } from 'node-cron'; -import { composeValidate, validateBooleanIs, validateJSONArrayOfStrings, validateLiteral, validateNumber, validateStringNoEmpty, validateStringNoSpaces } from './services/settings-validate'; +import { composeValidate, validateBooleanIs, validateJSONArrayOfStrings, validateLiteral, validateNumber, validateStringMultipleLines, validateStringNoEmpty, validateStringNoSpaces } from './services/settings-validate'; // Plugin export const PLUGIN_VERSION = version; @@ -849,6 +849,34 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableUI: true, requireReload: true, }, + "customization.reports.footer": { + title: "Reports footer", + description: "Set the footer of the reports.", + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.textarea, + default: "", + defaultHidden: REPORTS_PAGE_FOOTER_TEXT, + configurableFile: true, + configurableUI: true, + validate: validateStringMultipleLines({max: 2}), + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, + }, + "customization.reports.header": { + title: "Reports header", + description: "Set the header of the reports.", + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.textarea, + default: "", + defaultHidden: REPORTS_PAGE_HEADER_TEXT, + configurableFile: true, + configurableUI: true, + validate: validateStringMultipleLines({max: 4}), + validateBackend: function(schema){ + return schema.string({minLength: 1, validate: this.validate}); + }, + }, "disabled_roles": { title: "Disables roles", description: "Disabled the plugin visibility for users with the roles.", @@ -1336,7 +1364,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), validateBackend: function(schema){ return schema.string({minLength: 1, validate: this.validate}); - }, + } }, "timeout": { title: "Request timeout", diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts index df42e79cab..6ebfea43b7 100644 --- a/common/services/settings-validate.ts +++ b/common/services/settings-validate.ts @@ -21,6 +21,7 @@ export const validateStringNoEmpty = (value: string): string | undefined => { } }; }; + export const validateStringMultipleLines = (options: {min?: number, max?: number} = {}) => (value: number) => { const lines = value.split(/\r\n|\r|\n/).length; if(typeof options.min !== 'undefined' && lines < options.min){ diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx index dec089463b..9aa0d98ab4 100644 --- a/public/components/common/form/index.tsx +++ b/public/components/common/form/index.tsx @@ -3,6 +3,7 @@ import { IInputForm } from './types'; import { InputFormEditor } from './input_editor'; import { InputFormNumber } from './input_number'; import { InputFormText } from './input_text'; +import { InputFormTextArea } from './input_text_area'; import { InputFormSwitch } from './input_switch'; import { InputFormSelect } from './input_select'; import { useFormFieldChanged } from './hooks'; @@ -55,4 +56,5 @@ const Input = { number: InputFormNumber, select: InputFormSelect, text: InputFormText, + textarea: InputFormTextArea, }; diff --git a/public/components/common/form/input_text_area.tsx b/public/components/common/form/input_text_area.tsx new file mode 100644 index 0000000000..e41fbb45f8 --- /dev/null +++ b/public/components/common/form/input_text_area.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { + EuiTextArea, +} from '@elastic/eui'; +import { IInputFormType } from './types'; + +export const InputFormTextArea = ({ value, isInvalid, onChange } : IInputFormType) => { + return ( + + ); +}; diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index 7a2e09dba8..f87330f0da 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -10,13 +10,14 @@ import { import { log } from '../logger'; import * as TimSort from 'timsort'; import { getConfiguration } from '../get-configuration'; -import { REPORTS_PRIMARY_COLOR, REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, REPORTS_PAGE_FOOTER_TEXT, REPORTS_PAGE_HEADER_TEXT } from '../../../common/constants'; +import { REPORTS_PRIMARY_COLOR} from '../../../common/constants'; +import { getSettingDefaultValue } from '../../../common/services/settings'; const COLORS = { PRIMARY: REPORTS_PRIMARY_COLOR }; -const pageConfiguration = (nameLogo) => ({ +const pageConfiguration = ({pathToLogo, pageHeader, pageFooter}) => ({ styles: { h1: { fontSize: 22, @@ -54,11 +55,11 @@ const pageConfiguration = (nameLogo) => ({ margin: [40, 20, 0, 0], columns: [ { - image: path.join(__dirname, `../../../public/assets/${nameLogo}`), + image: path.join(__dirname, `../../../public/assets/${pathToLogo}`), width: 190 }, { - text: REPORTS_PAGE_HEADER_TEXT, + text: pageHeader, alignment: 'right', margin: [0, 0, 40, 0], color: COLORS.PRIMARY @@ -70,7 +71,7 @@ const pageConfiguration = (nameLogo) => ({ return { columns: [ { - text: REPORTS_PAGE_FOOTER_TEXT, + text: pageFooter, color: COLORS.PRIMARY, margin: [40, 40, 0, 0] }, @@ -614,9 +615,13 @@ export class ReportPrinter{ } async print(reportPath: string){ - const nameLogo = ( await getConfiguration() )['customization.logo.reports'] || REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH; + const configuration = await getConfiguration(); + + const pathToLogo = configuration['customization.logo.reports'] || getSettingDefaultValue('customization.logo.reports'); + const pageHeader = configuration['customization.reports.header'] || getSettingDefaultValue('customization.reports.header'); + const pageFooter = configuration['customization.reports.footer'] || getSettingDefaultValue('customization.reports.footer'); - const document = this._printer.createPdfKitDocument({...pageConfiguration(nameLogo), content: this._content}); + const document = this._printer.createPdfKitDocument({...pageConfiguration({pathToLogo, pageHeader, pageFooter}), content: this._content}); await document.pipe( fs.createWriteStream(reportPath) ); 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 13/79] 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 ce4652e8bec6e20e335183c34f47cc48a92e6058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 13 Sep 2022 13:02:19 +0200 Subject: [PATCH 14/79] fix(settings): Fix accessing to `validate` of undefined error --- server/routes/wazuh-utils/wazuh-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routes/wazuh-utils/wazuh-utils.ts b/server/routes/wazuh-utils/wazuh-utils.ts index 3e0b2c8567..e96075c4a1 100644 --- a/server/routes/wazuh-utils/wazuh-utils.ts +++ b/server/routes/wazuh-utils/wazuh-utils.ts @@ -31,9 +31,9 @@ export function WazuhUtilsRoutes(router: IRouter) { { path: '/utils/configuration', validate: { - body: schema.object(Object.entries(PLUGIN_SETTINGS).filter(([, {configurableFile}]) => configurableFile).reduce((accum, [pluginSettingKey, {validateBackend}]) => ({ + body: schema.object(Object.entries(PLUGIN_SETTINGS).filter(([, {configurableFile}]) => configurableFile).reduce((accum, [pluginSettingKey, pluginSettingConfiguration]) => ({ ...accum, - [pluginSettingKey]: schema.maybe(validateBackend ? (validateBackend)(schema) : schema.any()) + [pluginSettingKey]: schema.maybe(pluginSettingConfiguration.validateBackend ? pluginSettingConfiguration.validateBackend(schema) : schema.any()) }), {})) } }, 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 15/79] 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 acb8bf05ecf2312fefb9368665462848f86d8f49 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 14 Sep 2022 18:50:31 +0200 Subject: [PATCH 16/79] Fix custom logo ratio in PDF sheet --- server/lib/reporting/printer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index f87330f0da..c8afec0b2e 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -56,7 +56,7 @@ const pageConfiguration = ({pathToLogo, pageHeader, pageFooter}) => ({ columns: [ { image: path.join(__dirname, `../../../public/assets/${pathToLogo}`), - width: 190 + fit: [190,50] }, { text: pageHeader, 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 17/79] 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 18/79] 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 951812e5e8886167b5b6cb883b735bd23353bf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 19 Sep 2022 10:10:25 +0200 Subject: [PATCH 19/79] fix: Fixed some settings validation --- common/constants.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 7d70ef32f6..fa640124e0 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -662,7 +662,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableUI: true, validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), validateBackend: function(schema){ - return schema.string({minLength: 1, validate: this.validate}); + return schema.string({validate: this.validate}); }, }, "cron.statistics.apis": { @@ -690,8 +690,8 @@ export const PLUGIN_SETTINGS: TpluginSettings = { }, validate: validateJSONArrayOfStrings, validateBackend: function(schema){ - return schema.string(this.validate); - } + return schema.string({validate: this.validate}); + }, }, "cron.statistics.index.creation": { title: "Index creation", @@ -761,7 +761,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { return validateNumber(this.options.number)(value) }, validateBackend: function(schema){ - return schema.number({...this.options.number}); + return schema.number({validate: this.validate}); }, }, "cron.statistics.index.shards": { @@ -782,7 +782,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { return validateNumber(this.options.number)(value) }, validateBackend: function(schema){ - return schema.number({...this.options.number}); + return schema.number({validate: this.validate}); }, }, "cron.statistics.interval": { @@ -794,7 +794,9 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableFile: true, configurableUI: true, requireRestart: true, - validate: (value: string) => validateNodeCronInterval(value) ? undefined : "Interval is not valid.", + validate: function(value: string){ + return validateNodeCronInterval(value) ? undefined : "Interval is not valid." + }, validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -886,8 +888,8 @@ export const PLUGIN_SETTINGS: TpluginSettings = { }, validate: validateJSONArrayOfStrings, validateBackend: function(schema){ - return schema.string({validate: this.validate}); - } + return schema.arrayOf(schema.string({minLength: 1})); + }, }, "enrollment.dns": { title: "Enrollment DNS", @@ -897,9 +899,9 @@ export const PLUGIN_SETTINGS: TpluginSettings = { default: "", configurableFile: true, configurableUI: true, - validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validate: validateStringNoSpaces, validateBackend: function(schema){ - return schema.string({minLenght: 1, validate: this.validate}); + return schema.string({validate: this.validate}); }, }, "enrollment.password": { @@ -912,7 +914,7 @@ export const PLUGIN_SETTINGS: TpluginSettings = { configurableUI: false, validate: validateStringNoEmpty, validateBackend: function(schema){ - return schema.string({minLength: 1, validate: this.validate}); + return schema.string({validate: this.validate}); }, }, "extensions.audit": { From e0a057e6bba6bd05ee28d0f22ed3864aa26b0030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 19 Sep 2022 11:48:14 +0200 Subject: [PATCH 20/79] fix(settings): fixed validation of literals --- common/services/settings-validate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts index df42e79cab..bf2605c907 100644 --- a/common/services/settings-validate.ts +++ b/common/services/settings-validate.ts @@ -65,4 +65,4 @@ export const validateJSONArrayOfStrings = (value: string) => { }; }; -export const validateLiteral = (...literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}`; +export const validateLiteral = (literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}`; From 96993367eb60be8b24d944db64cfecdfd50dc418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 19 Sep 2022 11:49:42 +0200 Subject: [PATCH 21/79] fix(settings): removed unused import --- common/services/settings-validate.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts index bf2605c907..e5601a18ba 100644 --- a/common/services/settings-validate.ts +++ b/common/services/settings-validate.ts @@ -1,5 +1,3 @@ -import path from 'path'; - // Utils export const composeValidate = (...functions) => value => { for(const fn of functions){ 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 22/79] 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 23/79] 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 24/79] 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 25/79] 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 26/79] 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. +

+
+
+ +
+`; diff --git a/public/components/common/form/index.test.tsx b/public/components/common/form/index.test.tsx index 2540a923d1..db9843b17e 100644 --- a/public/components/common/form/index.test.tsx +++ b/public/components/common/form/index.test.tsx @@ -15,6 +15,7 @@ describe('[component] InputForm', () => { ${'select'} | ${'value1'} | ${{select: [{text: 'Label1', value: 'value1'}, {text: 'Label2', value: 'value2'}]}}} | ${{}} ${'switch'} | ${true} | ${{switch: {values: {enabled: {label: 'Enabled', value: true}, disabled: {label: 'Disabled', value: false}}}}} | ${{}} ${'text'} | ${'test'} | ${undefined} | ${{isInvalid: false}} + ${'textarea'} | ${'test'} | ${undefined} | ${{}} `('Renders correctly to match the snapshot. Input: $inputType', ({ inputType, value, options }) => { const wrapper = render( Date: Wed, 28 Sep 2022 17:50:20 +0200 Subject: [PATCH 46/79] Added category sorting + description + docs link --- common/constants.ts | 1873 +++++++++-------- common/services/settings.ts | 20 +- .../components/category/category.tsx | 56 +- .../settings/configuration/configuration.tsx | 26 +- 4 files changed, 1024 insertions(+), 951 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 47139b494e..5c1f31bedf 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -14,7 +14,7 @@ import { version } from '../package.json'; // Plugin export const PLUGIN_VERSION = version; -export const PLUGIN_VERSION_SHORT = version.split('.').splice(0,2).join('.'); +export const PLUGIN_VERSION_SHORT = version.split('.').splice(0, 2).join('.'); // Index patterns - Wazuh alerts export const WAZUH_INDEX_TYPE_ALERTS = 'alerts'; @@ -339,9 +339,9 @@ export const ELASTIC_NAME = 'elastic'; // Plugin settings -export enum SettingCategory{ - HEALTH_CHECK, +export enum SettingCategory { GENERAL, + HEALTH_CHECK, EXTENSIONS, MONITORING, STATISTICS, @@ -350,968 +350,981 @@ export enum SettingCategory{ }; type TPluginSettingOptionsSelect = { - select: {text: string, value: any}[] + select: { text: string, value: any }[] }; type TPluginSettingOptionsNumber = { - number: { - min?: number - max?: number - } + number: { + min?: number + max?: number + } }; type TPluginSettingOptionsEditor = { - editor: { - language: string - } + editor: { + language: string + } }; type TPluginSettingOptionsSwitch = { - switch: { - values: { - disabled: {label?: string, value: any}, - enabled: {label?: string, value: any}, - } - } + 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 enum EpluginSettingType { + text = 'text', + textarea = 'textarea', + switch = 'switch', + number = 'number', + editor = 'editor', + select = 'select', }; export type TPluginSetting = { - // Define the text displayed in the UI. - title: string - // Description. - description: string - // Category. - category: SettingCategory - // Type. - type: EpluginSettingType - // Default value. - defaultValue: any - // Default value if it is not set. It has preference over `default`. - defaultValueIfNotSet?: any - // Configurable from the configuration file. - isConfigurableFromFile: boolean - // Configurable from the UI (Settings/Configuration). - isConfigurableFromUI: boolean - // Modify the setting requires running the plugin health check (frontend). - requiresRunningHealthCheck?: boolean - // Modify the setting requires reloading the browser tab (frontend). - requiresReloadingBrowserTab?: boolean - // Modify the setting requires restarting the plugin platform to take effect. - requiresRestartingPluginPlatform?: boolean - // Define options related to the `type`. - options?: TPluginSettingOptionsNumber | TPluginSettingOptionsEditor | TPluginSettingOptionsSelect | TPluginSettingOptionsSwitch - // Transform the input value. The result is saved in the form global state of Settings/Configuration - uiFormTransformChangedInputValue?: (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 - uiFormTransformInputValueToConfigurationValue?: (value: any) => any + // Define the text displayed in the UI. + title: string + // Description. + description: string + // Category. + category: SettingCategory + // Type. + type: EpluginSettingType + // Default value. + defaultValue: any + // Default value if it is not set. It has preference over `default`. + defaultValueIfNotSet?: any + // Configurable from the configuration file. + isConfigurableFromFile: boolean + // Configurable from the UI (Settings/Configuration). + isConfigurableFromUI: boolean + // Modify the setting requires running the plugin health check (frontend). + requiresRunningHealthCheck?: boolean + // Modify the setting requires reloading the browser tab (frontend). + requiresReloadingBrowserTab?: boolean + // Modify the setting requires restarting the plugin platform to take effect. + requiresRestartingPluginPlatform?: boolean + // Define options related to the `type`. + options?: TPluginSettingOptionsNumber | TPluginSettingOptionsEditor | TPluginSettingOptionsSelect | TPluginSettingOptionsSwitch + // Transform the input value. The result is saved in the form global state of Settings/Configuration + uiFormTransformChangedInputValue?: (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 + uiFormTransformInputValueToConfigurationValue?: (value: any) => any }; export type TPluginSettingWithKey = TPluginSetting & { key: TPluginSettingKey }; +export type TPluginSettingCategory = { + title: string + description?: string + documentationLink?: string + renderOrder: number +}; 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" - }, + description: "Define which checks will be executed by the App's HealthCheck. Allowed values are: true, false", + renderOrder: SettingCategory.HEALTH_CHECK, + } as TPluginSettingCategory, [SettingCategory.GENERAL]: { title: 'General', - description: "General settings." - }, + description: "General settings.", + renderOrder: SettingCategory.GENERAL, + } as TPluginSettingCategory, [SettingCategory.EXTENSIONS]: { title: 'Extensions', - description: "Extensions." - }, + description: "Extensions.", + } as TPluginSettingCategory, [SettingCategory.SECURITY]: { title: 'Security', - description: "Security." - }, + description: "Security.", + renderOrder: SettingCategory.SECURITY, + } as TPluginSettingCategory, [SettingCategory.MONITORING]: { title: 'Task:Monitoring', - description: "Monitoring." - }, + description: "Monitoring.", + renderOrder: SettingCategory.MONITORING, + } as TPluginSettingCategory, [SettingCategory.STATISTICS]: { title: 'Task:Statistics', - description: "Statistics." - }, + description: "Statistics.", + renderOrder: SettingCategory.STATISTICS, + } as TPluginSettingCategory, [SettingCategory.CUSTOMIZATION]: { - title: 'Customization', - description: "Customization." - } + title: 'Custom branding', + description: "By default, the Wazuh app uses the Wazuh logo, but if you want to use custom branding elements such as logos, you can do so by editing the settings below.", + documentationLink: 'user-manual/wazuh-dashboard/config-file.html#logo-customization', + renderOrder: SettingCategory.CUSTOMIZATION, + } as TPluginSettingCategory }; -export const PLUGIN_SETTINGS: {[key: string]: TPluginSetting} = { - "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, - defaultValue: WAZUH_SAMPLE_ALERT_PREFIX, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - }, - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: WAZUH_STATISTICS_DEFAULT_PREFIX, - isConfigurableFromFile: true, - isConfigurableFromUI: 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, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json' - } - }, - uiFormTransformConfigurationValueToInputValue: function(value : any): any{ - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function(value: string): any{ - try{ - return JSON.parse(value); - }catch(error){ - return value; - }; - }, - }, - "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: { - select: [ - { - text: "Hourly", - value: "h" - }, - { - text: "Daily", - value: "d" - }, - { - text: "Weekly", - value: "w" - }, - { - text: "Monthly", - value: "m" - } - ] - }, - defaultValue: WAZUH_STATISTICS_DEFAULT_CREATION, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: WAZUH_STATISTICS_DEFAULT_NAME, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: WAZUH_STATISTICS_DEFAULT_CRON_FREQ, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: true, - }, - "cron.statistics.status": { - title: "Status", - description: "Enable or disable the statistics tasks.", - category: SettingCategory.STATISTICS, - type: EpluginSettingType.switch, - defaultValue: WAZUH_STATISTICS_DEFAULT_STATUS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: "", - isConfigurableFromFile: true, - isConfigurableFromUI: true, - }, - "customization.logo.healthcheck": { - title: "Logo Health Check", - description: `Customize the logo displayed in the plugin health check.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.text, - defaultValue: "", - isConfigurableFromFile: true, - isConfigurableFromUI: true, - }, - "customization.logo.reports": { - title: "Logo Reports", - description: `Customize the logo displayed in the PDF reports.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.text, - defaultValue: "", - defaultValueIfNotSet: REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - }, - "customization.logo.sidebar": { - title: "Logo Sidebar", - description: `Customize the logo of the category that belongs the plugin.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.text, - defaultValue: "", - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresReloadingBrowserTab: true, - }, - "disabled_roles": { - title: "Disables roles", - description: "Disabled the plugin visibility for users with the roles.", - category: SettingCategory.SECURITY, - type: EpluginSettingType.editor, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json' - } - }, - uiFormTransformConfigurationValueToInputValue: function(value : any): any{ - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function(value: string): any{ - try{ - return JSON.parse(value); - }catch(error){ - return value; - }; - }, - }, - "enrollment.dns": { - title: "Enrollment DNS", - description: "Specifies the Wazuh registration server, used for the agent enrollment.", - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: "", - isConfigurableFromFile: true, - isConfigurableFromUI: true, - }, - "enrollment.password": { - title: "Enrollment Password", - description: "Specifies the password used to authenticate during the agent enrollment.", - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: "", - isConfigurableFromFile: true, - isConfigurableFromUI: false, - }, - "extensions.audit": { - title: "System auditing", - description: "Enable or disable the Audit tab on Overview and Agents.", - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresReloadingBrowserTab: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json' - } - }, - uiFormTransformConfigurationValueToInputValue: function(value : any): any{ - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function(value: string): any{ - try{ - return JSON.parse(value); - }catch(error){ - return value; - }; - }, - }, - "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, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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: { - select: [ - { - text: "Info", - value: "info" - }, - { - text: "Debug", - value: "debug" - } - ] - }, - defaultValue: "info", - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: 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, - defaultValue: WAZUH_ALERTS_PATTERN, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: 20000, - isConfigurableFromFile: true, - isConfigurableFromUI: 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: { - select: [ - { - text: "Hourly", - value: "h" - }, - { - text: "Daily", - value: "d" - }, - { - text: "Weekly", - value: "w" - }, - { - text: "Monthly", - value: "m" - } - ] - }, - defaultValue: WAZUH_MONITORING_DEFAULT_CREATION, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - }, - "wazuh.monitoring.enabled": { - title: "Status", - description: "Enable or disable the wazuh-monitoring index creation and/or visualization.", - category: SettingCategory.MONITORING, - type: EpluginSettingType.switch, - defaultValue: WAZUH_MONITORING_DEFAULT_ENABLED, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: true, - options: { - switch: { - values: { - disabled: {label: 'false', value: false}, - enabled: {label: 'true', value: true}, - } - } - }, - uiFormTransformChangedInputValue: 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, - defaultValue: WAZUH_MONITORING_DEFAULT_FREQUENCY, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: 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, - defaultValue: WAZUH_MONITORING_PATTERN, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: 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, - defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - options: { - number: { - min: 1 - } - }, - } +export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { + "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, + defaultValue: WAZUH_SAMPLE_ALERT_PREFIX, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + }, + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: WAZUH_STATISTICS_DEFAULT_PREFIX, + isConfigurableFromFile: true, + isConfigurableFromUI: 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, + defaultValue: [], + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + editor: { + language: 'json' + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: any): any { + return JSON.stringify(value); + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): any { + try { + return JSON.parse(value); + } catch (error) { + return value; + }; + }, + }, + "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: { + select: [ + { + text: "Hourly", + value: "h" + }, + { + text: "Daily", + value: "d" + }, + { + text: "Weekly", + value: "w" + }, + { + text: "Monthly", + value: "m" + } + ] + }, + defaultValue: WAZUH_STATISTICS_DEFAULT_CREATION, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: WAZUH_STATISTICS_DEFAULT_NAME, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: WAZUH_STATISTICS_DEFAULT_CRON_FREQ, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: true, + }, + "cron.statistics.status": { + title: "Status", + description: "Enable or disable the statistics tasks.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.switch, + defaultValue: WAZUH_STATISTICS_DEFAULT_STATUS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: function (value: boolean | string): boolean { + return Boolean(value); + }, + }, + "customization.logo.app": { + title: "App main logo", + description: `This logo is used in the app main menu, at the top left corner.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "customization.logo.healthcheck": { + title: "Healthcheck logo", + description: `This logo is displayed during the Healthcheck routine of the app.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "customization.logo.reports": { + title: "PDF reports Logo", + description: `This logos is used in the PDF reports generated by the app. It's placed at the top left corner of every page of the PDF.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + defaultValueIfNotSet: REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "customization.logo.sidebar": { + title: "Navigation drawer logo", + description: `This is the logo for the app to display in the platform's navigation drawer, this is, the main sidebar collapsible menu.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresReloadingBrowserTab: true, + }, + "disabled_roles": { + title: "Disable roles", + description: "Disabled the plugin visibility for users with the roles.", + category: SettingCategory.SECURITY, + type: EpluginSettingType.editor, + defaultValue: [], + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + editor: { + language: 'json' + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: any): any { + return JSON.stringify(value); + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): any { + try { + return JSON.parse(value); + } catch (error) { + return value; + }; + }, + }, + "enrollment.dns": { + title: "Enrollment DNS", + description: "Specifies the Wazuh registration server, used for the agent enrollment.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "enrollment.password": { + title: "Enrollment password", + description: "Specifies the password used to authenticate during the agent enrollment.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: false, + }, + "extensions.audit": { + title: "System auditing", + description: "Enable or disable the Audit tab on Overview and Agents.", + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresReloadingBrowserTab: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: [], + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + editor: { + language: 'json' + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: any): any { + return JSON.stringify(value); + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): any { + try { + return JSON.parse(value); + } catch (error) { + return value; + }; + }, + }, + "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, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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: { + select: [ + { + text: "Info", + value: "info" + }, + { + text: "Debug", + value: "debug" + } + ] + }, + defaultValue: "info", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: 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, + defaultValue: WAZUH_ALERTS_PATTERN, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: 20000, + isConfigurableFromFile: true, + isConfigurableFromUI: 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: { + select: [ + { + text: "Hourly", + value: "h" + }, + { + text: "Daily", + value: "d" + }, + { + text: "Weekly", + value: "w" + }, + { + text: "Monthly", + value: "m" + } + ] + }, + defaultValue: WAZUH_MONITORING_DEFAULT_CREATION, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "wazuh.monitoring.enabled": { + title: "Status", + description: "Enable or disable the wazuh-monitoring index creation and/or visualization.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.switch, + defaultValue: WAZUH_MONITORING_DEFAULT_ENABLED, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: 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, + defaultValue: WAZUH_MONITORING_DEFAULT_FREQUENCY, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: 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, + defaultValue: WAZUH_MONITORING_PATTERN, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: 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, + defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + options: { + number: { + min: 1 + } + }, + } }; export type TPluginSettingKey = keyof typeof PLUGIN_SETTINGS; diff --git a/common/services/settings.ts b/common/services/settings.ts index 8f53bbe2f5..ffc6a04f58 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -1,4 +1,20 @@ -import { EpluginSettingType, PLUGIN_SETTINGS, TPluginSetting, TPluginSettingKey, TPluginSettingWithKey } from '../constants'; +import { + EpluginSettingType, + PLUGIN_SETTINGS, + PLUGIN_SETTINGS_CATEGORIES, + TPluginSetting, + TPluginSettingKey, + TPluginSettingWithKey +} from '../constants'; + +/** + * Look for a configuration category setting by its name + * @param categoryTitle + * @returns category settings + */ +export function getCategorySettingByTitle(categoryTitle: string): any { + return Object.entries(PLUGIN_SETTINGS_CATEGORIES).find(([key, category]) => category?.title == categoryTitle)?.[1]; +} /** * Get the default value of the plugin setting. @@ -109,6 +125,8 @@ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ description, ...(options?.select ? [`Allowed values: ${options.select.map(({text, value}) => formatLabelValuePair(text, value)).join(', ')}.`] : []), ...(options?.switch ? [`Allowed values: ${['enabled', 'disabled'].map(s => formatLabelValuePair(options.switch.values[s].label, options.switch.values[s].value)).join(', ')}.`] : []), + ...(options?.number?.min ? [`Minimum value: ${options.number.min}.`] : []), + ...(options?.number?.max ? [`Maximum value: ${options.number.max}.`] : []), ].join(' '); }; 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 e7cf799298..f116940cf2 100644 --- a/public/components/settings/configuration/components/categories/components/category/category.tsx +++ b/public/components/settings/configuration/components/categories/components/category/category.tsx @@ -20,42 +20,74 @@ import { EuiForm, EuiDescribedFormGroup, EuiTitle, - EuiFormRow + EuiSpacer, + EuiToolTip, + EuiButtonIcon, } from '@elastic/eui'; import { EuiIconTip } from '@elastic/eui'; -import { EpluginSettingType, TPluginSettingWithKey, UI_LOGGER_LEVELS } from '../../../../../../../../common/constants'; +import { TPluginSettingWithKey } from '../../../../../../../../common/constants'; import { getPluginSettingDescription } from '../../../../../../../../common/services/settings'; +import { webDocumentationLink } from '../../../../../../../../common/services/web_documentation'; 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 { title: string + description?: string + documentationLink?: string items: TPluginSettingWithKey[] currentConfiguration: { [field: string]: any } changedConfiguration: { [field: string]: any } onChangeFieldForm: () => void } -export const Category: React.FunctionComponent = ({ title, items }) => { +export const Category: React.FunctionComponent = ({ + title, + description, + documentationLink, + items +}) => { return ( -

{title}

+

{title}{ + documentationLink && + + <> +   + + + + } +

+ { + description && + <> + + + + {description} + + + + + + } {items.map((item, idx) => { const isUpdated = item.changed && !item.error; diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index 362b9aa91a..430f24ae32 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -31,7 +31,7 @@ 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, groupSettingsByCategory } from '../../../../common/services/settings'; +import { formatSettingValueFromForm, getSettingsDefaultList, groupSettingsByCategory, getCategorySettingByTitle } from '../../../../common/services/settings'; import _ from 'lodash'; import { Category } from './components/categories/components'; import { WzRequest } from '../../../react-services'; @@ -110,7 +110,12 @@ const WzConfigurationSettingsProvider = (props) => { // https://github.com/elastic/eui/blob/aa4cfd7b7c34c2d724405a3ecffde7fe6cf3b50f/src/components/search_bar/query/query.ts#L138-L163 const search = Query.execute(query.query || query, visibleSettings, ['description', 'key', 'title']); - const visibleCategories = groupSettingsByCategory(search || visibleSettings); + const visibleCategories = groupSettingsByCategory(search || visibleSettings) + // Sort categories to render them in their enum definition order + .sort((a, b) => + (getCategorySettingByTitle(a.category)?.renderOrder || 0) - + (getCategorySettingByTitle(b.category)?.renderOrder || 0) + ); const onSave = async () => { setLoading(true); @@ -190,13 +195,18 @@ const WzConfigurationSettingsProvider = (props) => { }]}/> - {visibleCategories && visibleCategories.map(({category, settings}) => ( - + {visibleCategories && visibleCategories.map(({ category, settings }) => { + const { description, documentationLink } = getCategorySettingByTitle(category); + return ( + ) + } )} From a007e01f4453bd05457cacb4155b1ac4da25e138 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 28 Sep 2022 18:10:24 +0200 Subject: [PATCH 47/79] Added settings sorting within their category --- common/services/settings.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/common/services/settings.ts b/common/services/settings.ts index ffc6a04f58..2e92708b67 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -102,13 +102,15 @@ const formatSettingValueToFileType = { * @returns */ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ - const settingsSortedByCategories = settings.reduce((accum, pluginSettingConfiguration) => ({ - ...accum, - [pluginSettingConfiguration.category]: [ - ...(accum[pluginSettingConfiguration.category] || []), - {...pluginSettingConfiguration} - ] - }),{}); + const settingsSortedByCategories = settings + .sort((settingA, settingB) => settingA.key.localeCompare(settingB.key)) + .reduce((accum, pluginSettingConfiguration) => ({ + ...accum, + [pluginSettingConfiguration.category]: [ + ...(accum[pluginSettingConfiguration.category] || []), + { ...pluginSettingConfiguration } + ] + }), {}); return Object.entries(settingsSortedByCategories) .map(([category, settings]) => ({ category, settings })) From 0549cb62e6a3a161be6b6e1177dabb9f09b3580b Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 28 Sep 2022 18:22:00 +0200 Subject: [PATCH 48/79] Fixed constant types definition --- common/constants.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 5c1f31bedf..4c3f1b438b 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -422,46 +422,46 @@ export type TPluginSettingCategory = { title: string description?: string documentationLink?: string - renderOrder: number + renderOrder?: number }; -export const PLUGIN_SETTINGS_CATEGORIES = { +export const PLUGIN_SETTINGS_CATEGORIES: { [category: number]: TPluginSettingCategory } = { [SettingCategory.HEALTH_CHECK]: { title: 'Health check', description: "Define which checks will be executed by the App's HealthCheck. Allowed values are: true, false", renderOrder: SettingCategory.HEALTH_CHECK, - } as TPluginSettingCategory, + }, [SettingCategory.GENERAL]: { title: 'General', description: "General settings.", renderOrder: SettingCategory.GENERAL, - } as TPluginSettingCategory, + }, [SettingCategory.EXTENSIONS]: { title: 'Extensions', description: "Extensions.", - } as TPluginSettingCategory, + }, [SettingCategory.SECURITY]: { title: 'Security', description: "Security.", renderOrder: SettingCategory.SECURITY, - } as TPluginSettingCategory, + }, [SettingCategory.MONITORING]: { title: 'Task:Monitoring', description: "Monitoring.", renderOrder: SettingCategory.MONITORING, - } as TPluginSettingCategory, + }, [SettingCategory.STATISTICS]: { title: 'Task:Statistics', description: "Statistics.", renderOrder: SettingCategory.STATISTICS, - } as TPluginSettingCategory, + }, [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', description: "By default, the Wazuh app uses the Wazuh logo, but if you want to use custom branding elements such as logos, you can do so by editing the settings below.", documentationLink: 'user-manual/wazuh-dashboard/config-file.html#logo-customization', renderOrder: SettingCategory.CUSTOMIZATION, - } as TPluginSettingCategory + } }; export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { From 26fa66c11d76afeb82d85cce5bdc5b85b7111b2d Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 28 Sep 2022 18:26:16 +0200 Subject: [PATCH 49/79] Checks if localCompare exists validation --- common/services/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/services/settings.ts b/common/services/settings.ts index 2e92708b67..c02d33e859 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -103,7 +103,7 @@ const formatSettingValueToFileType = { */ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ const settingsSortedByCategories = settings - .sort((settingA, settingB) => settingA.key.localeCompare(settingB.key)) + .sort((settingA, settingB) => settingA.key?.localeCompare?.(settingB.key)) .reduce((accum, pluginSettingConfiguration) => ({ ...accum, [pluginSettingConfiguration.category]: [ From 1f4c01a4b9fa51a31c1c90be97ad85245dcfc4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 3 Oct 2022 11:11:10 +0200 Subject: [PATCH 50/79] fix(settings): fixed plugin setting description doesn't display the minimum number value when it is falsy (0) --- common/services/settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/services/settings.ts b/common/services/settings.ts index c02d33e859..76965a13cf 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -127,8 +127,8 @@ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ description, ...(options?.select ? [`Allowed values: ${options.select.map(({text, value}) => formatLabelValuePair(text, value)).join(', ')}.`] : []), ...(options?.switch ? [`Allowed values: ${['enabled', 'disabled'].map(s => formatLabelValuePair(options.switch.values[s].label, options.switch.values[s].value)).join(', ')}.`] : []), - ...(options?.number?.min ? [`Minimum value: ${options.number.min}.`] : []), - ...(options?.number?.max ? [`Maximum value: ${options.number.max}.`] : []), + ...(options?.number && 'min' in options.number ? [`Minimum value: ${options.number.min}.`] : []), + ...(options?.number && 'max' in options.number ? [`Maximum value: ${options.number.max}.`] : []), ].join(' '); }; From 33bc999556dad35b63dd97f5540899380773fcf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 3 Oct 2022 11:20:59 +0200 Subject: [PATCH 51/79] fix(settings): fix setting type of `wazuh.monitoring.replicas` and limit the valid number for the number input --- common/constants.ts | 2 +- public/components/common/form/input_number.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 4c3f1b438b..8bae0272aa 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -1299,7 +1299,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { title: "Index replicas", description: "Define the number of replicas to use for the wazuh-monitoring-* indices.", category: SettingCategory.MONITORING, - type: EpluginSettingType.text, + type: EpluginSettingType.number, defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, isConfigurableFromFile: true, isConfigurableFromUI: true, diff --git a/public/components/common/form/input_number.tsx b/public/components/common/form/input_number.tsx index 01d93a4cc8..479661b27c 100644 --- a/public/components/common/form/input_number.tsx +++ b/public/components/common/form/input_number.tsx @@ -4,13 +4,13 @@ import { } from '@elastic/eui'; import { IInputFormType } from './types'; -export const InputFormNumber = ({ field, value, onChange }: IInputFormType) => { +export const InputFormNumber = ({ options, value, onChange }: IInputFormType) => { return ( ); }; From ffb1eb9072ef8c3f217ee1ff6887d41ee9e003af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 3 Oct 2022 12:54:02 +0200 Subject: [PATCH 52/79] feat(settins): add plugin settings category description --- common/constants.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 8bae0272aa..b8b63506d7 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -429,36 +429,36 @@ export type TPluginSettingCategory = { export const PLUGIN_SETTINGS_CATEGORIES: { [category: number]: TPluginSettingCategory } = { [SettingCategory.HEALTH_CHECK]: { title: 'Health check', - description: "Define which checks will be executed by the App's HealthCheck. Allowed values are: true, false", + description: "Checks will be executed by the app's Healthcheck.", renderOrder: SettingCategory.HEALTH_CHECK, }, [SettingCategory.GENERAL]: { title: 'General', - description: "General settings.", + description: "Basic app settings related to alerts index pattern, hide the manager alerts in the dashboards, logs level and more.", renderOrder: SettingCategory.GENERAL, }, [SettingCategory.EXTENSIONS]: { - title: 'Extensions', + title: 'Initial display state of the modules of the new API host entries.', description: "Extensions.", }, [SettingCategory.SECURITY]: { title: 'Security', - description: "Security.", + description: "Application security options such as unauthorized roles.", renderOrder: SettingCategory.SECURITY, }, [SettingCategory.MONITORING]: { title: 'Task:Monitoring', - description: "Monitoring.", + description: "Options related to the agent status monitoring job and its storage in indexes.", renderOrder: SettingCategory.MONITORING, }, [SettingCategory.STATISTICS]: { title: 'Task:Statistics', - description: "Statistics.", + description: "Options related to the daemons manager monitoring job and their storage in indexes..", renderOrder: SettingCategory.STATISTICS, }, [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', - description: "By default, the Wazuh app uses the Wazuh logo, but if you want to use custom branding elements such as logos, you can do so by editing the settings below.", + description: "If you want to use custom branding elements such as logos, you can do so by editing the settings below.", documentationLink: 'user-manual/wazuh-dashboard/config-file.html#logo-customization', renderOrder: SettingCategory.CUSTOMIZATION, } From a935861227f5103a0e9758d9c8f170738781f78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 3 Oct 2022 13:07:28 +0200 Subject: [PATCH 53/79] fix(settings): fix a problem comparing the initial and current value for the plugin settings of the `number` type --- common/constants.ts | 36 +++++++++++++++++++ common/services/settings.ts | 20 ----------- .../settings/configuration/configuration.tsx | 4 +-- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index b8b63506d7..cdc3bd0c1a 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -722,6 +722,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { min: 0 } }, + uiFormTransformConfigurationValueToInputValue: function(value: number): string { + return String(value); + }, + uiFormTransformInputValueToConfigurationValue: function(value: string): number { + return Number(value); + }, }, "cron.statistics.index.shards": { title: "Index shards", @@ -737,6 +743,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { min: 1 } }, + uiFormTransformConfigurationValueToInputValue: function(value: number){ + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function(value: string): number { + return Number(value); + }, }, "cron.statistics.interval": { title: "Interval", @@ -1218,6 +1230,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { min: 1500 } }, + uiFormTransformConfigurationValueToInputValue: function(value: number){ + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function(value: string): number { + return Number(value); + }, }, "wazuh.monitoring.creation": { title: "Index creation", @@ -1284,6 +1302,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { min: 60 } }, + uiFormTransformConfigurationValueToInputValue: function(value: number){ + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function(value: string): number { + return Number(value); + }, }, "wazuh.monitoring.pattern": { title: "Index pattern", @@ -1309,6 +1333,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { min: 0 } }, + uiFormTransformConfigurationValueToInputValue: function(value: number){ + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function(value: string): number { + return Number(value); + }, }, "wazuh.monitoring.shards": { title: "Index shards", @@ -1324,6 +1354,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { min: 1 } }, + uiFormTransformConfigurationValueToInputValue: function(value: number){ + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function(value: string): number { + return Number(value); + }, } }; diff --git a/common/services/settings.ts b/common/services/settings.ts index 76965a13cf..f01eea8647 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -60,26 +60,6 @@ export function getSettingsDefaultList(): TPluginSettingWithKey[] { ]), []); }; -/** - * - * @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, -}; - /** * 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 diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index 430f24ae32..5a6b7424c8 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -31,7 +31,7 @@ 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, groupSettingsByCategory, getCategorySettingByTitle } from '../../../../common/services/settings'; +import { getSettingsDefaultList, groupSettingsByCategory, getCategorySettingByTitle } from '../../../../common/services/settings'; import _ from 'lodash'; import { Category } from './components/categories/components'; import { WzRequest } from '../../../react-services'; @@ -124,7 +124,7 @@ const WzConfigurationSettingsProvider = (props) => { if(PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile){ accum.saveOnConfigurationFile = { ...accum.saveOnConfigurationFile, - [pluginSettingKey]: formatSettingValueFromForm(pluginSettingKey, currentValue) + [pluginSettingKey]: currentValue } }; return accum; From a9621886650ab29a992e85cfa9d1e336aef5c989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 3 Oct 2022 15:04:21 +0200 Subject: [PATCH 54/79] fix(settings): fix wrong conflict resolution --- common/constants.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/common/constants.ts b/common/constants.ts index 5623f46a21..a4c81504c7 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -899,6 +899,34 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresReloadingBrowserTab: true, }, + "customization.reports.footer": { + title: "Reports footer", + description: "Set the footer of the reports.", + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.textarea, + defaultValue: "", + defaultValueIfNotSet: REPORTS_PAGE_FOOTER_TEXT, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + validate: validateStringMultipleLines({max: 2}), + validateBackend: function(schema){ + return schema.string({validate: this.validate}); + }, + }, + "customization.reports.header": { + title: "Reports header", + description: "Set the header of the reports.", + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.textarea, + defaultValue: "", + defaultValueIfNotSet: REPORTS_PAGE_HEADER_TEXT, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + validate: validateStringMultipleLines({max: 4}), + validateBackend: function(schema){ + return schema.string({validate: this.validate}); + }, + }, "disabled_roles": { title: "Disable roles", description: "Disabled the plugin visibility for users with the roles.", From b551b006bf9cdeb68eebf107cb629f0b70bbcc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 3 Oct 2022 15:06:47 +0200 Subject: [PATCH 55/79] fix(settings): fix typo in setting description --- common/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.ts b/common/constants.ts index cdc3bd0c1a..29cf6c01ea 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -800,7 +800,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { }, "customization.logo.reports": { title: "PDF reports Logo", - description: `This logos is used in the PDF reports generated by the app. It's placed at the top left corner of every page of the PDF.`, + description: `This logo is used in the PDF reports generated by the app. It's placed at the top left corner of every page of the PDF.`, category: SettingCategory.CUSTOMIZATION, type: EpluginSettingType.text, defaultValue: "", From f5bf16ff2db5e3d60bcafb1c024eeaa268b65877 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Mon, 3 Oct 2022 19:00:15 +0200 Subject: [PATCH 56/79] Add set custom header footer unit test --- server/lib/reporting/printer.ts | 28 +-- server/routes/wazuh-reporting.test.ts | 236 +++++++++++------- server/routes/wazuh-utils/wazuh-utils.test.ts | 2 +- 3 files changed, 162 insertions(+), 104 deletions(-) diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index c8afec0b2e..88726ef929 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -474,7 +474,7 @@ export class ReportPrinter{ this.addContent(typeof title === 'string' ? { text: title, style: 'h4' } : title) .addNewLine(); } - + if (!items || !items.length) { this.addContent({ text: 'No results match your search criteria', @@ -495,23 +495,23 @@ export class ReportPrinter{ style: 'standard' } }) - }); + }); // 385 is the max initial width per column let totalLength = columns.length - 1; const widthColumn = 385/totalLength; let totalWidth = totalLength * widthColumn; - + const widths:(number)[] = []; - + for (let step = 0; step < columns.length - 1; step++) { let columnLength = this.getColumnWidth(columns[step], tableRows, step); - + if (columnLength <= Math.round(totalWidth / totalLength)) { widths.push(columnLength); totalWidth -= columnLength; - } + } else { widths.push(Math.round(totalWidth / totalLength)); totalWidth -= Math.round((totalWidth / totalLength)); @@ -519,7 +519,7 @@ export class ReportPrinter{ totalLength--; } widths.push('*'); - + this.addContent({ fontSize: 8, table: { @@ -563,9 +563,9 @@ export class ReportPrinter{ `agents: ${agents}`, 'debug' ); - + this.addNewLine(); - + this.addContent({ text: 'NOTE: This report only includes the authorized agents of the user who generated the report', @@ -616,7 +616,7 @@ export class ReportPrinter{ async print(reportPath: string){ const configuration = await getConfiguration(); - + const pathToLogo = configuration['customization.logo.reports'] || getSettingDefaultValue('customization.logo.reports'); const pageHeader = configuration['customization.reports.header'] || getSettingDefaultValue('customization.reports.header'); const pageFooter = configuration['customization.reports.footer'] || getSettingDefaultValue('customization.reports.footer'); @@ -630,10 +630,10 @@ export class ReportPrinter{ /** * Returns the width of a given column - * - * @param column - * @param tableRows - * @param step + * + * @param column + * @param tableRows + * @param step * @returns {number} */ getColumnWidth(column, tableRows, index){ diff --git a/server/routes/wazuh-reporting.test.ts b/server/routes/wazuh-reporting.test.ts index b56ea7b2ff..352f14d1d9 100644 --- a/server/routes/wazuh-reporting.test.ts +++ b/server/routes/wazuh-reporting.test.ts @@ -1,96 +1,154 @@ // To launch this file // yarn test:jest --testEnvironment node --verbose server/routes/wazuh-reporting -import axios from 'axios'; -import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants'; - -function buildAxiosOptions(method: string, path: string, data: any = {}, headers: any = {}){ - return { - method: method, - headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json', ...headers }, - url: `http://localhost:5601${path}`, - data: data - }; +import { Router } from '../../../../src/core/server/http/router/router'; +import { HttpServer } from '../../../../src/core/server/http/http_server'; +import { loggingSystemMock } from '../../../../src/core/server/logging/logging_system.mock'; +import { ByteSizeValue } from '@kbn/config-schema'; +import supertest from 'supertest'; +import { WazuhUtilsRoutes } from './wazuh-utils'; +import { WazuhReportingRoutes } from './wazuh-reporting'; +import { WazuhUtilsCtrl } from '../controllers/wazuh-utils/wazuh-utils'; +import md5 from 'md5'; +import { ReportPrinter } from '../lib/reporting/printer'; + + +import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../lib/filesystem'; +import { + WAZUH_DATA_CONFIG_APP_PATH, + WAZUH_DATA_CONFIG_DIRECTORY_PATH, + WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, + WAZUH_DATA_LOGS_DIRECTORY_PATH, + WAZUH_DATA_LOGS_RAW_PATH, + PLUGIN_PLATFORM_REQUEST_HEADERS, + WAZUH_DATA_ABSOLUTE_PATH +} from '../../common/constants'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +jest.mock('../lib/reporting/extended-information', () => ({ + extendedInformation: jest.fn() +})); + +const loggingService = loggingSystemMock.create(); +const logger = loggingService.get(); +const context = { + wazuh: { + security: { + getCurrentUser: (request) => { + // x-test-username header doesn't exist when the platform or plugin are running. + // It is used to generate the output of this method so we can simulate the user + // that does the request to the endpoint and is expected by the endpoint handlers + // of the plugin. + const username = request.headers['x-test-username']; + return { username, hashUsername: md5(username) } + } + } + } }; +const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context); +let server, innerServer; + +beforeAll(async () => { + // Create /data/wazuh directory. + createDataDirectoryIfNotExists(); + // Create /data/wazuh/config directory. + createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); + + // Create /data/wazuh/logs directory. + createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); + + // Create server + const config = { + name: 'plugin_platform', + host: '127.0.0.1', + maxPayload: new ByteSizeValue(1024), + port: 10002, + ssl: { enabled: false }, + compression: { enabled: true }, + requestId: { + allowFromAnyIp: true, + ipAllowlist: [], + }, + } as any; + server = new HttpServer(loggingService, 'tests'); + const router = new Router('', logger, enhanceWithContext); + const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config); + innerServer = innerServerTest; + + const spyRouteDecoratorProtectedAdministratorRoleValidToken = jest.spyOn(WazuhUtilsCtrl.prototype as any, 'routeDecoratorProtectedAdministratorRoleValidToken') + .mockImplementation((handler) => async (...args) => handler(...args)); + // const spycheckReportsUserDirectoryIsValidRouteDecorator = jest.spyOn(WazuhReportingCtrl.prototype as any, 'checkReportsUserDirectoryIsValidRouteDecorator') + // .mockImplementation((handler) => async (...args) => handler(...args)); + + // Register routes + WazuhUtilsRoutes(router); + WazuhReportingRoutes(router); + + // Register router + registerRouter(router); + + // start server + await server.start(); +}); + +afterAll(async () => { + // Stop server + await server.stop(); -describe.skip('Wazuh Reporting', () => { - describe('Wazuh API - /reports', () => { - test('[200] Returns the available reports for user', () => { - const options = buildAxiosOptions('get', '/reports', {}, { - cookie: 'wz-user=elastic' - }); - return axios(options).then(response => { - expect(response.status).toBe(200); - expect(Array.isArray(response.data.reports)).toBe(true); - }).catch(error => {throw error}) - }); + // Clear all mocks + jest.clearAllMocks(); + + // Remove /data/wazuh directory. + execSync(`rm -rf ${WAZUH_DATA_ABSOLUTE_PATH}`); +}); + +describe('[endpoint] PUT /utils/configuration', () => { + beforeAll(() => { + // Create the configuration file with custom content + const fileContent = `--- + pattern: test-alerts-* + + hosts: + - default: + url: https://localhost + port: 55000 + username: wazuh-wui + password: wazuh-wui + run_as: false + `; + + fs.writeFileSync(WAZUH_DATA_CONFIG_APP_PATH, fileContent, 'utf8'); + }); + + afterAll(() => { + // Remove the configuration file + fs.unlinkSync(WAZUH_DATA_CONFIG_APP_PATH); }); - //TODO: do the test for these endpoints - // describe('Wazuh API - /reports/{name}', () => { - // test('[200] Returns the available reports for user and name', () => { - // const options = buildAxiosOptions('get', '/reports/wazuh-report.pdf', {}, { - // cookie: 'wz-user=elastic' - // }); - // return axios(options).then(response => { - // expect(response.status).toBe(200); - // expect(Array.isArray(response.data.reports)).toBe(true); - // }).catch(error => {throw error}) - // }); - - // test('[200] Returns the available reports for user and name', () => { - // const options = buildAxiosOptions('delete', '/reports/wazuh-report.pdf', {}, { - // cookie: 'wz-user=elastic' - // }); - // return axios(options).then(response => { - // }).catch(error => {throw error}) - // }); - // }); - - // describe('Wazuh API - /reports/modules/{moduleID}', () => { - // test('[200] Generates a modules reports the available reports for user and name', () => { - // const options = buildAxiosOptions('post', '/reports/modules/{moduleID}', {}, { - // cookie: 'wz-user=elastic' - // }); - // return axios(options).then(response => { - // expect(response.status).toBe(200); - // expect(Array.isArray(response.data.reports)).toBe(true); - // }).catch(error => {throw error}) - // }); - // }); - - // describe('Wazuh API - /reports/groups/{groupID}', () => { - // test('[200] Generates a modules reports the available reports for user and name', () => { - // const options = buildAxiosOptions('post', '/reports/groups/{groupID}', {}, { - // cookie: 'wz-user=elastic' - // }); - // return axios(options).then(response => { - // expect(response.status).toBe(200); - // expect(Array.isArray(response.data.reports)).toBe(true); - // }).catch(error => {throw error}) - // }); - // }); - - // describe('Wazuh API - /reports/agents/{agentID}', () => { - // test('[200] Generates a modules reports the available reports for user and name', () => { - // const options = buildAxiosOptions('post', '/reports/agents/{agentID}', {}, { - // cookie: 'wz-user=elastic' - // }); - // return axios(options).then(response => { - // expect(response.status).toBe(200); - // expect(Array.isArray(response.data.reports)).toBe(true); - // }).catch(error => {throw error}) - // }); - // }); - - // describe('Wazuh API - /reports/agents/{agentID}/inventory', () => { - // test('[200] Generates a modules reports the available reports for user and name', () => { - // const options = buildAxiosOptions('post', '/reports/agents/{agentID}/inventory', {}, { - // cookie: 'wz-user=elastic' - // }); - // return axios(options).then(response => { - // expect(response.status).toBe(200); - // expect(Array.isArray(response.data.reports)).toBe(true); - // }).catch(error => {throw error}) - // }); - // }); + it(`Set custom report header and footer - PUT /utils/configuration - $responseStatusCode`, async () => { + const configurationBody = { + ['customization.reports.footer']: 'Test\nTest', + ['customization.reports.header']: 'info@company.com\nFake Avenue 123' + }; + const fromDate = new Date(); + const toDate = new Date(); + + fromDate.setHours(toDate.getHours() - 1); + const reportBody = { "array": [], "filters": [], "time": { "from": fromDate.toISOString(), "to": toDate.toISOString() }, "searchBar": "", "tables": [], "tab": "general", "section": "overview", "agents": false, "browserTimezone": "Europe/Madrid", "indexPatternTitle": "wazuh-alerts-*", "apiId": "default" }; + + const responseConfig = await supertest(innerServer.listener) + .put('/utils/configuration') + .send(configurationBody) + .expect(200); + + expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.footer']).toMatch(configurationBody['customization.reports.footer']); + expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.header']).toMatch(configurationBody['customization.reports.header']); + + const responseReport = await supertest(innerServer.listener) + .post(`/reports/modules/general`) + .set('x-test-username', 'admin') + .send(reportBody) + .expect(200); + }); }); diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts index 599aa95915..13c8080002 100644 --- a/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -29,7 +29,7 @@ beforeAll(async () => { createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); // Create /data/wazuh/logs directory. - createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); + createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); // Create server const config = { From a4e202ea052ff4550817b9b738113fa2b9d94359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 4 Oct 2022 14:35:35 +0200 Subject: [PATCH 57/79] feat(settings): enhance the validation of plugin settings related to indices or index patterns taking in account the supported characters --- common/constants.ts | 89 ++++++++++++++++--- common/services/settings-validate.ts | 41 +++++---- server/routes/wazuh-utils/wazuh-utils.test.ts | 88 ++++++++++++++++++ 3 files changed, 190 insertions(+), 28 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 0a33eab5a6..f42341a27b 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -12,7 +12,7 @@ import path from 'path'; import { version } from '../package.json'; import { validate as validateNodeCronInterval } from 'node-cron'; -import { composeValidate, validateBooleanIs, validateJSONArrayOfStrings, validateLiteral, validateNumber, validateStringNoEmpty, validateStringNoSpaces } from './services/settings-validate'; +import { composeValidate, validateBooleanIs, validateLiteral, validateNumber, validateStringNoInvalidCharacters, validateStringNoEmpty, validateStringNoLiteral, validateStringNoSpaces, validateStringNoStartWith, validateJSON, validateObjectArray, validateStringIs } from './services/settings-validate'; // Plugin export const PLUGIN_VERSION = version; @@ -479,7 +479,13 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromFile: true, isConfigurableFromUI: true, requiresRunningHealthCheck: true, - validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc + validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') + ), validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -684,7 +690,13 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { defaultValue: WAZUH_STATISTICS_DEFAULT_PREFIX, isConfigurableFromFile: true, isConfigurableFromUI: true, - validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc + validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') + ), validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -712,9 +724,18 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return value; }; }, - validate: validateJSONArrayOfStrings, + validate: validateJSON(composeValidate( + validateObjectArray(composeValidate( + validateStringIs, + validateStringNoEmpty, + validateStringNoSpaces, + )), + )), validateBackend: function(schema){ - return schema.arrayOf(schema.string({validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces)})); + return schema.arrayOf(schema.string({validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + )})); }, }, "cron.statistics.index.creation": { @@ -762,7 +783,13 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromFile: true, isConfigurableFromUI: true, requiresRunningHealthCheck: true, - validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc + validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') + ), validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -922,9 +949,18 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return value; }; }, - validate: validateJSONArrayOfStrings, + validate: validateJSON(composeValidate( + validateObjectArray(composeValidate( + validateStringIs, + validateStringNoEmpty, + validateStringNoSpaces, + )), + )), validateBackend: function(schema){ - return schema.arrayOf(schema.string({validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces)})); + return schema.arrayOf(schema.string({validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + )})); }, }, "enrollment.dns": { @@ -1313,9 +1349,25 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return value; }; }, - validate: validateJSONArrayOfStrings, + // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc + validate: validateJSON(composeValidate( + validateObjectArray(composeValidate( + validateStringIs, + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoLiteral('.', '..'), + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + )), + )), validateBackend: function(schema){ - return schema.arrayOf(schema.string({validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces)})); + return schema.arrayOf(schema.string({validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoLiteral('.', '..'), + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + )})); }, }, "ip.selector": { @@ -1379,7 +1431,14 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromFile: true, isConfigurableFromUI: true, requiresRunningHealthCheck: true, - validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc + validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoLiteral('.', '..'), + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + ), validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -1507,7 +1566,13 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromFile: true, isConfigurableFromUI: true, requiresRunningHealthCheck: true, - validate: composeValidate(validateStringNoEmpty, validateStringNoSpaces), + validate: composeValidate( + validateStringNoEmpty, + validateStringNoSpaces, + validateStringNoLiteral('.', '..'), + validateStringNoStartWith('-', '_', '+', '.'), + validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + ), validateBackend: function(schema){ return schema.string({minLength: 1, validate: this.validate}); }, diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts index f4d06edf96..9aa9a875f3 100644 --- a/common/services/settings-validate.ts +++ b/common/services/settings-validate.ts @@ -9,6 +9,7 @@ export const composeValidate = (...functions) => value => { }; // String +export const validateStringIs = (value: unknown): string | undefined => typeof value === 'string' ? undefined : "Value is not a string."; export const validateStringNoSpaces = (value: string): string | undefined => /^\S*$/.test(value) ? undefined : "It can't contain spaces."; export const validateStringNoEmpty = (value: string): string | undefined => { if(typeof value === 'string'){ @@ -29,6 +30,12 @@ export const validateStringMultipleLines = (options: {min?: number, max?: number }; }; +export const validateStringNoInvalidCharacters = (...invalidCharacters: string[]) => (value: string): string | undefined => invalidCharacters.some(invalidCharacter => value.includes(invalidCharacter)) ? `It can't contain invalid characters: ${invalidCharacters.join(', ')}.` : undefined; + +export const validateStringNoStartWith = (...invalidStartingCharacters: string[]) => (value: string): string | undefined => invalidStartingCharacters.some(invalidStartingCharacter => value.startsWith(invalidStartingCharacter)) ? `It can't start with: ${invalidStartingCharacters.join(', ')}.` : undefined; + +export const validateStringNoLiteral = (...invalidLiterals: string[]) => (value: string): string | undefined => invalidLiterals.some(invalidLiteral => value === invalidLiteral) ? `It can't be: ${invalidLiterals.join(', ')}.` : undefined; + // Boolean export const validateBooleanIs = (value: string): string | undefined => typeof value === 'boolean' ? undefined : "It should be a boolean. Allowed values: true or false."; @@ -43,34 +50,36 @@ export const validateNumber = (options: {min?: number, max?: number} = {}) => (v }; // Complex -export const validateJSONArrayOfStrings = (value: string) => { - let parsed; +export const validateJSON = (validateParsed: (object: any) => string | undefined) => (value: string) => { + let jsonObject; // Try to parse the string as JSON try{ - parsed = JSON.parse(value); + jsonObject = JSON.parse(value); }catch(error){ return "Value can't be parsed. There is some error."; }; + return validateParsed ? validateParsed(jsonObject) : undefined; +}; + +export const validateObjectArray = (validationElement: (json: any) => string | undefined) => (value: unknown[]) => { // Check the JSON is an array - if(!Array.isArray(parsed)){ + if(!Array.isArray(value)){ return 'Value is not a valid list.'; }; - // Check the items are strings - if(parsed.some(value => typeof value !== 'string')){ - return 'There is a value that is not a string.'; - }; + return validationElement ? value.reduce((accum, elementValue) => { + if(accum){ + return accum; + }; - // Check the items are strings - for(let element of parsed){ - const result = validateStringNoEmptyNoSpaces(element); - if(result){ - return result; + const resultValidationElement = validationElement(elementValue); + if(resultValidationElement){ + return resultValidationElement; }; - }; -}; -export const validateStringNoEmptyNoSpaces = composeValidate(validateStringNoEmpty, validateStringNoSpaces); + return accum; + }, undefined) : undefined; +}; export const validateLiteral = (literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}`; diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts index e9d7990ad0..57bf691dfb 100644 --- a/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -157,6 +157,20 @@ hosts: ${'alerts.sample.prefix'} | ${''} | ${400} | ${"[request body.alerts.sample.prefix]: Value can not be empty."} ${'alerts.sample.prefix'} | ${'test space'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain spaces."} ${'alerts.sample.prefix'} | ${4} | ${400} | ${'[request body.alerts.sample.prefix]: expected value of type [string] but got [number]'} + ${'alerts.sample.prefix'} | ${'-test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'_test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'+test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'.test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'test\\'}| ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test/'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test?'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test"'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test<'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test>'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test|'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test,'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test#'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test*'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} ${'checks.api'} | ${true} | ${200} | ${null} ${'checks.api'} | ${0} | ${400} | ${'[request body.checks.api]: expected value of type [boolean] but got [number]'} ${'checks.fields'} | ${true} | ${200} | ${null} @@ -175,9 +189,24 @@ hosts: ${'cron.prefix'} | ${'test space'} | ${400} | ${"[request body.cron.prefix]: It can't contain spaces."} ${'cron.prefix'} | ${''} | ${400} | ${"[request body.cron.prefix]: Value can not be empty."} ${'cron.prefix'} | ${4} | ${400} | ${'[request body.cron.prefix]: expected value of type [string] but got [number]'} + ${'cron.prefix'} | ${'-test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'_test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'+test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'.test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'test\\'}| ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test/'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test?'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test"'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test<'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test>'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test|'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test,'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test#'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test*'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} ${'cron.statistics.apis'} | ${['test']} | ${200} | ${null} ${'cron.statistics.apis'} | ${['test ']} | ${400} | ${"[request body.cron.statistics.apis.0]: It can't contain spaces."} ${'cron.statistics.apis'} | ${['']} | ${400} | ${"[request body.cron.statistics.apis.0]: Value can not be empty."} + ${'cron.statistics.apis'} | ${['test', 4]} | ${400} | ${"[request body.cron.statistics.apis.1]: expected value of type [string] but got [number]"} ${'cron.statistics.apis'} | ${'test space'} | ${400} | ${"[request body.cron.statistics.apis]: could not parse array value from json input"} ${'cron.statistics.apis'} | ${true} | ${400} | ${"[request body.cron.statistics.apis]: expected value of type [array] but got [boolean]"} ${'cron.statistics.index.creation'} | ${'h'} | ${200} | ${null} @@ -189,6 +218,20 @@ hosts: ${'cron.statistics.index.name'} | ${''} | ${400} | ${"[request body.cron.statistics.index.name]: Value can not be empty."} ${'cron.statistics.index.name'} | ${'test space'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain spaces."} ${'cron.statistics.index.name'} | ${true} | ${400} | ${"[request body.cron.statistics.index.name]: expected value of type [string] but got [boolean]"} + ${'cron.statistics.index.name'} | ${'-test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'_test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'+test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'.test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'test\\'}| ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test/'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test?'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test"'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test<'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test>'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test|'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test,'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test#'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test*'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} ${'cron.statistics.index.replicas'} | ${0} | ${200} | ${null} ${'cron.statistics.index.replicas'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.replicas]: Value should be greater or equal than 0."} ${'cron.statistics.index.replicas'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.index.replicas]: expected value of type [number] but got [string]"} @@ -204,6 +247,7 @@ hosts: ${'disabled_roles'} | ${['test']} | ${200} | ${null} ${'disabled_roles'} | ${['']} | ${400} | ${'[request body.disabled_roles.0]: Value can not be empty.'} ${'disabled_roles'} | ${['test space']} | ${400} | ${"[request body.disabled_roles.0]: It can't contain spaces."} + ${'disabled_roles'} | ${['test', 4]} | ${400} | ${"[request body.disabled_roles.1]: expected value of type [string] but got [number]"} ${'enrollment.dns'} | ${'test'} | ${200} | ${null} ${'enrollment.dns'} | ${''} | ${200} | ${null} ${'enrollment.dns'} | ${'test space'} | ${400} | ${"[request body.enrollment.dns]: It can't contain spaces."} @@ -237,17 +281,47 @@ hosts: ${'extensions.virustotal'} | ${true} | ${200} | ${null} ${'extensions.virustotal'} | ${0} | ${400} | ${'[request body.extensions.virustotal]: expected value of type [boolean] but got [number]'} ${'ip.ignore'} | ${['test']} | ${200} | ${null} + ${'ip.ignore'} | ${['test*']} | ${200} | ${null} ${'ip.ignore'} | ${['']} | ${400} | ${'[request body.ip.ignore.0]: Value can not be empty.'} ${'ip.ignore'} | ${['test space']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain spaces."} + ${'ip.ignore'} | ${true} | ${400} | ${"[request body.ip.ignore.0]: It can't contain spaces."} + ${'ip.ignore'} | ${['-test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['_test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['+test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['.test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['test\\']}| ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test/']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test?']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test"']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test<']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test>']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test|']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test,']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test#']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test', 'test#']} | ${400} | ${"[request body.ip.ignore.1]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} ${'ip.selector'} | ${true} | ${200} | ${null} ${'ip.selector'} | ${''} | ${400} | ${'[request body.ip.selector]: expected value of type [boolean] but got [string]'} ${'logs.level'} | ${'info'} | ${200} | ${null} ${'logs.level'} | ${'debug'} | ${200} | ${null} ${'logs.level'} | ${''} | ${400} | ${'[request body.logs.level]: types that failed validation:\n- [request body.logs.level.0]: expected value to equal [info]\n- [request body.logs.level.1]: expected value to equal [debug]'} ${'pattern'} | ${'test'} | ${200} | ${null} + ${'pattern'} | ${'test*'} | ${200} | ${null} ${'pattern'} | ${''} | ${400} | ${'[request body.pattern]: Value can not be empty.'} ${'pattern'} | ${'test space'} | ${400} | ${"[request body.pattern]: It can't contain spaces."} ${'pattern'} | ${true} | ${400} | ${'[request body.pattern]: expected value of type [string] but got [boolean]'} + ${'pattern'} | ${'-test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'_test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'+test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'.test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'test\\'}| ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test/'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test?'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test"'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test<'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test>'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test|'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test,'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test#'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'timeout'} | ${15000} | ${200} | ${null} ${'timeout'} | ${1000} | ${400} | ${'[request body.timeout]: Value should be greater or equal than 1500.'} ${'timeout'} | ${''} | ${400} | ${'[request body.timeout]: expected value of type [number] but got [string]'} @@ -262,8 +336,22 @@ hosts: ${'wazuh.monitoring.frequency'} | ${40} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Value should be greater or equal than 60."} ${'wazuh.monitoring.frequency'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.frequency]: expected value of type [number] but got [string]'} ${'wazuh.monitoring.pattern'} | ${'test'} | ${200} | ${null} + ${'wazuh.monitoring.pattern'} | ${'test*'} | ${200} | ${null} ${'wazuh.monitoring.pattern'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.pattern]: value has length [0] but it must have a minimum length of [1].'} ${'wazuh.monitoring.pattern'} | ${true} | ${400} | ${'[request body.wazuh.monitoring.pattern]: expected value of type [string] but got [boolean]'} + ${'wazuh.monitoring.pattern'} | ${'-test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'_test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'+test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'.test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'test\\'}| ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test/'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test?'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test"'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test<'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test>'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test|'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test,'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test#'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'wazuh.monitoring.replicas'} | ${0} | ${200} | ${null} ${'wazuh.monitoring.replicas'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Value should be greater or equal than 0."} ${'wazuh.monitoring.replicas'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.replicas]: expected value of type [number] but got [string]"} From 08792c9389413e663b71cc263107dc42b6728abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Tue, 4 Oct 2022 16:34:06 +0200 Subject: [PATCH 58/79] feat(settings): add validation of setting values in the inputs --- common/plugin-settings.test.ts | 205 ++++++++++++++++++ common/services/settings-validate.ts | 2 +- server/routes/wazuh-utils/wazuh-utils.test.ts | 5 +- 3 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 common/plugin-settings.test.ts diff --git a/common/plugin-settings.test.ts b/common/plugin-settings.test.ts new file mode 100644 index 0000000000..35d636a545 --- /dev/null +++ b/common/plugin-settings.test.ts @@ -0,0 +1,205 @@ +import { PLUGIN_SETTINGS } from "./constants"; + +describe('[settings] Input validation', () => { + it.each` + setting | value | expectedValidation + ${'alerts.sample.prefix'} | ${'test'} | ${undefined} + ${'alerts.sample.prefix'} | ${''} | ${"Value can not be empty."} + ${'alerts.sample.prefix'} | ${'test space'} | ${"It can't contain spaces."} + ${'alerts.sample.prefix'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'checks.api'} | ${true} | ${undefined} + ${'checks.api'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.fields'} | ${true} | ${undefined} + ${'checks.fields'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.maxBuckets'} | ${true} | ${undefined} + ${'checks.maxBuckets'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.pattern'} | ${true} | ${undefined} + ${'checks.pattern'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.setup'} | ${true} | ${undefined} + ${'checks.setup'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.template'} | ${true} | ${undefined} + ${'checks.template'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.timeFilter'} | ${true} | ${undefined} + ${'checks.timeFilter'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'cron.prefix'} | ${'test'} | ${undefined} + ${'cron.prefix'} | ${'test space'} | ${"It can't contain spaces."} + ${'cron.prefix'} | ${''} | ${"Value can not be empty."} + ${'cron.prefix'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.apis'} | ${['test']} | ${undefined} + ${'cron.statistics.apis'} | ${['test ']} | ${"It can't contain spaces."} + ${'cron.statistics.apis'} | ${['']} | ${"Value can not be empty."} + ${'cron.statistics.apis'} | ${['test', 4]} | ${"Value is not a string."} + ${'cron.statistics.apis'} | ${'test space'} | ${"Value is not a valid list."} + ${'cron.statistics.apis'} | ${true} | ${"Value is not a valid list."} + ${'cron.statistics.index.creation'} | ${'h'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'d'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'w'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'m'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'test'} | ${"Invalid value. Allowed values: h, d, w, m."} + ${'cron.statistics.index.name'} | ${'test'} | ${undefined} + ${'cron.statistics.index.name'} | ${''} | ${"Value can not be empty."} + ${'cron.statistics.index.name'} | ${'test space'} | ${"It can't contain spaces."} + ${'cron.statistics.index.name'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.replicas'} | ${0} | ${undefined} + ${'cron.statistics.index.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} + ${'cron.statistics.index.shards'} | ${1} | ${undefined} + ${'cron.statistics.index.shards'} | ${-1} | ${"Value should be greater or equal than 1."} + ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${undefined} + ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${undefined} + ${'cron.statistics.interval'} | ${'custom'} | ${"Interval is not valid."} + ${'cron.statistics.interval'} | ${true} | ${"Interval is not valid."} + ${'cron.statistics.status'} | ${true} | ${undefined} + ${'cron.statistics.status'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'disabled_roles'} | ${['test']} | ${undefined} + ${'disabled_roles'} | ${['']} | ${'Value can not be empty.'} + ${'disabled_roles'} | ${['test space']} | ${"It can't contain spaces."} + ${'disabled_roles'} | ${['test', 4]} | ${"Value is not a string."} + ${'enrollment.dns'} | ${'test'} | ${undefined} + ${'enrollment.dns'} | ${''} | ${undefined} + ${'enrollment.dns'} | ${'test space'} | ${"It can't contain spaces."} + ${'enrollment.password'} | ${'test'} | ${undefined} + ${'enrollment.password'} | ${''} | ${"Value can not be empty."} + ${'enrollment.password'} | ${'test space'} | ${undefined} + ${'extensions.audit'} | ${true} | ${undefined} + ${'extensions.audit'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.aws'} | ${true} | ${undefined} + ${'extensions.aws'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.ciscat'} | ${true} | ${undefined} + ${'extensions.ciscat'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.gcp'} | ${true} | ${undefined} + ${'extensions.gcp'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.gdpr'} | ${true} | ${undefined} + ${'extensions.gdpr'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.hipaa'} | ${true} | ${undefined} + ${'extensions.hipaa'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.nist'} | ${true} | ${undefined} + ${'extensions.nist'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.oscap'} | ${true} | ${undefined} + ${'extensions.oscap'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.osquery'} | ${true} | ${undefined} + ${'extensions.osquery'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.pci'} | ${true} | ${undefined} + ${'extensions.pci'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.tsc'} | ${true} | ${undefined} + ${'extensions.tsc'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.virustotal'} | ${true} | ${undefined} + ${'extensions.virustotal'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'ip.ignore'} | ${['test']} | ${undefined} + ${'ip.ignore'} | ${['test*']} | ${undefined} + ${'ip.ignore'} | ${['']} | ${'Value can not be empty.'} + ${'ip.ignore'} | ${['test space']} | ${"It can't contain spaces."} + ${'ip.ignore'} | ${true} | ${"Value is not a valid list."} + ${'ip.ignore'} | ${['-test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['_test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['+test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['.test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['test\\']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test/']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test?']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test"']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test<']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test>']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test|']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test,']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test#']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test', 'test#']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.selector'} | ${true} | ${undefined} + ${'ip.selector'} | ${''} | ${'It should be a boolean. Allowed values: true or false.'} + ${'logs.level'} | ${'info'} | ${undefined} + ${'logs.level'} | ${'debug'} | ${undefined} + ${'logs.level'} | ${''} | ${'Invalid value. Allowed values: info, debug.'} + ${'pattern'} | ${'test'} | ${undefined} + ${'pattern'} | ${'test*'} | ${undefined} + ${'pattern'} | ${''} | ${'Value can not be empty.'} + ${'pattern'} | ${'test space'} | ${"It can't contain spaces."} + ${'pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'timeout'} | ${15000} | ${undefined} + ${'timeout'} | ${1000} | ${'Value should be greater or equal than 1500.'} + ${'timeout'} | ${''} | ${'Value should be greater or equal than 1500.'} + ${'wazuh.monitoring.creation'} | ${'h'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'d'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'w'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'m'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'test'} | ${"Invalid value. Allowed values: h, d, w, m."} + ${'wazuh.monitoring.enabled'} | ${true} | ${undefined} + ${'wazuh.monitoring.frequency'} | ${100} | ${undefined} + ${'wazuh.monitoring.frequency'} | ${40} | ${"Value should be greater or equal than 60."} + ${'wazuh.monitoring.pattern'} | ${'test'} | ${undefined} + ${'wazuh.monitoring.pattern'} | ${'test*'} | ${undefined} + ${'wazuh.monitoring.pattern'} | ${''} | ${'Value can not be empty.'} + ${'wazuh.monitoring.pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.replicas'} | ${0} | ${undefined} + ${'wazuh.monitoring.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} + ${'wazuh.monitoring.shards'} | ${1} | ${undefined} + ${'wazuh.monitoring.shards'} | ${-1} | ${"Value should be greater or equal than 1."} + `('$setting | $value | $expectedValidation', ({ setting, value, expectedValidation }) => { + expect( + PLUGIN_SETTINGS[setting].validate( + PLUGIN_SETTINGS[setting]?.uiFormTransformConfigurationValueToInputValue?.(value) + ?? value + )).toBe(expectedValidation); + }) +}); diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts index 9aa9a875f3..92947d64fa 100644 --- a/common/services/settings-validate.ts +++ b/common/services/settings-validate.ts @@ -82,4 +82,4 @@ export const validateObjectArray = (validationElement: (json: any) => string | u }, undefined) : undefined; }; -export const validateLiteral = (literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}`; +export const validateLiteral = (literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}.`; diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts index 57bf691dfb..99db7481a4 100644 --- a/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -237,7 +237,6 @@ hosts: ${'cron.statistics.index.replicas'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.index.replicas]: expected value of type [number] but got [string]"} ${'cron.statistics.index.shards'} | ${1} | ${200} | ${null} ${'cron.statistics.index.shards'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.shards]: Value should be greater or equal than 1."} - ${'cron.statistics.index.shards'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.shards]: Value should be greater or equal than 1."} ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${200} | ${null} ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${200} | ${null} ${'cron.statistics.interval'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.interval]: Interval is not valid."} @@ -284,7 +283,7 @@ hosts: ${'ip.ignore'} | ${['test*']} | ${200} | ${null} ${'ip.ignore'} | ${['']} | ${400} | ${'[request body.ip.ignore.0]: Value can not be empty.'} ${'ip.ignore'} | ${['test space']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain spaces."} - ${'ip.ignore'} | ${true} | ${400} | ${"[request body.ip.ignore.0]: It can't contain spaces."} + ${'ip.ignore'} | ${true} | ${400} | ${"[request body.ip.ignore]: expected value of type [array] but got [boolean]"} ${'ip.ignore'} | ${['-test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} ${'ip.ignore'} | ${['_test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} ${'ip.ignore'} | ${['+test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} @@ -298,7 +297,7 @@ hosts: ${'ip.ignore'} | ${['test|']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'ip.ignore'} | ${['test,']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'ip.ignore'} | ${['test#']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test', 'test#']} | ${400} | ${"[request body.ip.ignore.1]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'ip.ignore'} | ${['test', 'test#']} | ${400} | ${"[request body.ip.ignore.1]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'ip.selector'} | ${true} | ${200} | ${null} ${'ip.selector'} | ${''} | ${400} | ${'[request body.ip.selector]: expected value of type [boolean] but got [string]'} ${'logs.level'} | ${'info'} | ${200} | ${null} From 51f96cb482dd74920b3d02ac2d1a2212473e3c27 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Tue, 4 Oct 2022 20:19:24 +0200 Subject: [PATCH 59/79] Added unit test to verify the PDF report integrity --- server/controllers/wazuh-reporting.ts | 6 +- server/lib/reporting/printer.ts | 31 +++++++--- server/routes/wazuh-reporting.test.ts | 88 +++++++++++++++++---------- 3 files changed, 80 insertions(+), 45 deletions(-) diff --git a/server/controllers/wazuh-reporting.ts b/server/controllers/wazuh-reporting.ts index 417eeda095..65bd8ac66f 100644 --- a/server/controllers/wazuh-reporting.ts +++ b/server/controllers/wazuh-reporting.ts @@ -66,9 +66,9 @@ export class WazuhReportingCtrl { str += `${ type === 'range' ? `${params.gte}-${params.lt}` - : type === 'phrases' - ? '(' + params.join(" OR ") + ')' - : type === 'exists' + : type === 'phrases' + ? '(' + params.join(" OR ") + ')' + : type === 'exists' ? '*' : !!value ? value diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index 88726ef929..3f6205edbf 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -12,6 +12,7 @@ import * as TimSort from 'timsort'; import { getConfiguration } from '../get-configuration'; import { REPORTS_PRIMARY_COLOR} from '../../../common/constants'; import { getSettingDefaultValue } from '../../../common/services/settings'; +import { try } from 'bluebird'; const COLORS = { PRIMARY: REPORTS_PRIMARY_COLOR @@ -614,18 +615,28 @@ export class ReportPrinter{ ); } - async print(reportPath: string){ - const configuration = await getConfiguration(); + async print(reportPath: string) { + return new Promise((resolve, reject) => { + try { + const configuration = getConfiguration(); - const pathToLogo = configuration['customization.logo.reports'] || getSettingDefaultValue('customization.logo.reports'); - const pageHeader = configuration['customization.reports.header'] || getSettingDefaultValue('customization.reports.header'); - const pageFooter = configuration['customization.reports.footer'] || getSettingDefaultValue('customization.reports.footer'); + const pathToLogo = configuration['customization.logo.reports'] || getSettingDefaultValue('customization.logo.reports'); + const pageHeader = configuration['customization.reports.header'] || getSettingDefaultValue('customization.reports.header'); + const pageFooter = configuration['customization.reports.footer'] || getSettingDefaultValue('customization.reports.footer'); - const document = this._printer.createPdfKitDocument({...pageConfiguration({pathToLogo, pageHeader, pageFooter}), content: this._content}); - await document.pipe( - fs.createWriteStream(reportPath) - ); - document.end(); + const document = this._printer.createPdfKitDocument({ ...pageConfiguration({ pathToLogo, pageHeader, pageFooter }), content: this._content }); + + document.on('error', reject); + document.on('end', resolve); + + document.pipe( + fs.createWriteStream(reportPath) + ); + document.end(); + } catch (ex) { + reject(ex); + } + }); } /** diff --git a/server/routes/wazuh-reporting.test.ts b/server/routes/wazuh-reporting.test.ts index 352f14d1d9..e338f7cb0e 100644 --- a/server/routes/wazuh-reporting.test.ts +++ b/server/routes/wazuh-reporting.test.ts @@ -9,18 +9,14 @@ import { WazuhUtilsRoutes } from './wazuh-utils'; import { WazuhReportingRoutes } from './wazuh-reporting'; import { WazuhUtilsCtrl } from '../controllers/wazuh-utils/wazuh-utils'; import md5 from 'md5'; -import { ReportPrinter } from '../lib/reporting/printer'; import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../lib/filesystem'; import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_DATA_CONFIG_DIRECTORY_PATH, - WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_RAW_PATH, - PLUGIN_PLATFORM_REQUEST_HEADERS, WAZUH_DATA_ABSOLUTE_PATH } from '../../common/constants'; import { execSync } from 'child_process'; @@ -29,7 +25,7 @@ import fs from 'fs'; jest.mock('../lib/reporting/extended-information', () => ({ extendedInformation: jest.fn() })); - +const USER_NAME = 'admin'; const loggingService = loggingSystemMock.create(); const logger = loggingService.get(); const context = { @@ -49,9 +45,11 @@ const context = { const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context); let server, innerServer; +// BEFORE ALL beforeAll(async () => { // Create /data/wazuh directory. createDataDirectoryIfNotExists(); + // Create /data/wazuh/config directory. createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); @@ -76,10 +74,9 @@ beforeAll(async () => { const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config); innerServer = innerServerTest; - const spyRouteDecoratorProtectedAdministratorRoleValidToken = jest.spyOn(WazuhUtilsCtrl.prototype as any, 'routeDecoratorProtectedAdministratorRoleValidToken') + // Mock decorator + jest.spyOn(WazuhUtilsCtrl.prototype as any, 'routeDecoratorProtectedAdministratorRoleValidToken') .mockImplementation((handler) => async (...args) => handler(...args)); - // const spycheckReportsUserDirectoryIsValidRouteDecorator = jest.spyOn(WazuhReportingCtrl.prototype as any, 'checkReportsUserDirectoryIsValidRouteDecorator') - // .mockImplementation((handler) => async (...args) => handler(...args)); // Register routes WazuhUtilsRoutes(router); @@ -126,29 +123,56 @@ describe('[endpoint] PUT /utils/configuration', () => { fs.unlinkSync(WAZUH_DATA_CONFIG_APP_PATH); }); - it(`Set custom report header and footer - PUT /utils/configuration - $responseStatusCode`, async () => { - const configurationBody = { - ['customization.reports.footer']: 'Test\nTest', - ['customization.reports.header']: 'info@company.com\nFake Avenue 123' - }; - const fromDate = new Date(); - const toDate = new Date(); - - fromDate.setHours(toDate.getHours() - 1); - const reportBody = { "array": [], "filters": [], "time": { "from": fromDate.toISOString(), "to": toDate.toISOString() }, "searchBar": "", "tables": [], "tab": "general", "section": "overview", "agents": false, "browserTimezone": "Europe/Madrid", "indexPatternTitle": "wazuh-alerts-*", "apiId": "default" }; - - const responseConfig = await supertest(innerServer.listener) - .put('/utils/configuration') - .send(configurationBody) - .expect(200); - - expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.footer']).toMatch(configurationBody['customization.reports.footer']); - expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.header']).toMatch(configurationBody['customization.reports.header']); - - const responseReport = await supertest(innerServer.listener) - .post(`/reports/modules/general`) - .set('x-test-username', 'admin') - .send(reportBody) - .expect(200); + // expectedMD5 variable is a verified md5 of a report generated with this header and footer + // if any of the parameters is changed this variable should be updated with the new md5 + it.each` + footer | header | responseStatusCode | expectedMD5 + ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'}| ${200} | ${'0acbd4ee321699791b080b45c11dfe2b'} +`(`Set custom report header and footer - Verify PDF output`, async ({footer, header, responseStatusCode, expectedMD5}) => { + + // Mock PDF report parameters + const reportBody = { "array": [], "filters": [], "time": { "from": '2022-10-01T09:59:40.825Z', "to": '2022-10-04T09:59:40.825Z' }, "searchBar": "", "tables": [], "tab": "general", "section": "overview", "agents": false, "browserTimezone": "Europe/Madrid", "indexPatternTitle": "wazuh-alerts-*", "apiId": "default" }; + + // Define custom configuration + const configurationBody = {}; + + if (typeof footer == 'string') { + configurationBody['customization.reports.footer'] = footer; + } + if (typeof header == 'string') { + configurationBody['customization.reports.header'] = header; + } + + // Set custom report header and footer + if (typeof footer == 'string' || typeof header == 'string') { + const responseConfig = await supertest(innerServer.listener) + .put('/utils/configuration') + .send(configurationBody) + .expect(responseStatusCode); + + if (typeof footer == 'string') { + expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.footer']).toMatch(configurationBody['customization.reports.footer']); + } + if (typeof header == 'string') { + expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.header']).toMatch(configurationBody['customization.reports.header']); + } + } + + const responseReport = await supertest(innerServer.listener) + .post(`/reports/modules/general`) + .set('x-test-username', USER_NAME) + .send(reportBody) + .expect(200); + const fileName = responseReport.body?.message.match(/([A-Z-0-9]*\.pdf)/gi)[0]; + const userPath = md5(USER_NAME); + const reportPath = `${WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH}/${userPath}/${fileName}`; + const PDFbuffer = fs.readFileSync(reportPath); + const PDFcontent = PDFbuffer.toString('utf8'); + const content = PDFcontent + .replace(/\[<[a-z0-9].+> <[a-z0-9].+>\]/gi, '') + .replace(/(obj\n\(D:[0-9].+Z\)\nendobj)/gi, ''); + const PDFmd5 = md5(content); + + expect(PDFmd5).toBe(expectedMD5); }); }); From 624a8f4cc581002ad4ae5038e6138b484a48d6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Wed, 5 Oct 2022 08:52:33 +0200 Subject: [PATCH 60/79] fix(tests): format tables of the tests --- common/services/settings.test.ts | 24 +++++----- public/components/common/form/index.test.tsx | 15 +++--- server/lib/initial-wazuh-config.test.ts | 46 ++++++++++++------- server/routes/wazuh-utils/wazuh-utils.test.ts | 4 +- 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/common/services/settings.test.ts b/common/services/settings.test.ts index 0317e71da8..474236e172 100644 --- a/common/services/settings.test.ts +++ b/common/services/settings.test.ts @@ -7,9 +7,9 @@ describe('[settings] Methods', () => { describe('formatLabelValuePair: Format the label-value pairs used to display the allowed values', () => { it.each` - label | value | expected - ${'TestLabel'} | ${true} | ${'true (TestLabel)'} - ${'true'} | ${true} | ${'true'} + label | value | expected + ${'TestLabel'} | ${true} | ${'true (TestLabel)'} + ${'true'} | ${true} | ${'true'} `(`label: $label | value: $value | expected: $expected`, ({ label, expected, value }) => { expect(formatLabelValuePair(label, value)).toBe(expected); }); @@ -17,15 +17,15 @@ describe('[settings] Methods', () => { describe('formatSettingValueToFile: Format setting values to save in the configuration file', () => { it.each` - input | expected - ${'test'} | ${'\"test\"'} - ${'test space'} | ${'\"test space\"'} - ${'test\nnew line'} | ${'\"test\\nnew line\"'} - ${''} | ${'\"\"'} - ${1} | ${1} - ${true} | ${true} - ${false} | ${false} - ${['test1']} | ${'[\"test1\"]'} + input | expected + ${'test'} | ${'\"test\"'} + ${'test space'} | ${'\"test space\"'} + ${'test\nnew line'} | ${'\"test\\nnew line\"'} + ${''} | ${'\"\"'} + ${1} | ${1} + ${true} | ${true} + ${false} | ${false} + ${['test1']} | ${'[\"test1\"]'} ${['test1', 'test2']} | ${'[\"test1\",\"test2\"]'} `(`input: $input | expected: $expected`, ({ input, expected }) => { expect(formatSettingValueToFile(input)).toBe(expected); diff --git a/public/components/common/form/index.test.tsx b/public/components/common/form/index.test.tsx index 2223deb7ab..f982571cf7 100644 --- a/public/components/common/form/index.test.tsx +++ b/public/components/common/form/index.test.tsx @@ -7,13 +7,16 @@ jest.mock('../../../../../../node_modules/@elastic/eui/lib/services/accessibilit })); describe('[component] InputForm', () => { + const optionsEditor = {editor: {language: 'json'}}; + const optionsSelect = {select: [{text: 'Label1', value: 'value1'}, {text: 'Label2', value: 'value2'}]}; + const optionsSwitch = {switch: {values: {enabled: {label: 'Enabled', value: true}, disabled: {label: 'Disabled', value: false}}}}; it.each` - inputType | value | options | rest - ${'editor'} | ${'{}'} | ${{editor: {language: 'json'}}} | ${{}} - ${'number'} | ${4} | ${undefined} | ${{}} - ${'select'} | ${'value1'} | ${{select: [{text: 'Label1', value: 'value1'}, {text: 'Label2', value: 'value2'}]}}} | ${{}} - ${'switch'} | ${true} | ${{switch: {values: {enabled: {label: 'Enabled', value: true}, disabled: {label: 'Disabled', value: false}}}}} | ${{}} - ${'text'} | ${'test'} | ${undefined} | ${{isInvalid: false}} + inputType | value | options + ${'editor'} | ${'{}'} | ${optionsEditor} + ${'number'} | ${4} | ${undefined} + ${'select'} | ${'value1'} | ${optionsSelect} + ${'switch'} | ${true} | ${optionsSwitch} + ${'text'} | ${'test'} | ${undefined} `('Renders correctly to match the snapshot: Input: $inputType', ({ inputType, value, options }) => { const wrapper = render( { describe('[configuration-file] Methods', () => { it.each` - text | options + text | options ${'Test'} | ${{}} ${'Test'} | ${{ maxLength: 60, prefix: '# '}} ${'Test'} | ${{ maxLength: 60, fill: '-', prefix: '# '}} @@ -56,8 +56,8 @@ describe('[configuration-file] Methods', () => { }); it.each` - input | expected - ${{title: 'Test', description: 'Test description'}} | ${'# ------------------------------------ Test ------------------------------------\n#\n# Test description'} + input | expected + ${{title: 'Test', description: 'Test description'}} | ${'# ------------------------------------ Test ------------------------------------\n#\n# Test description'} ${{title: 'Test 2', description: 'Test description'}} | ${'# ----------------------------------- Test 2 -----------------------------------\n#\n# Test description'} ${{title: 'Test 2', description: 'Test description'}} | ${'# ----------------------------------- Test 2 -----------------------------------\n#\n# Test description'} `('printSettingValue: input: $input , expected: $expected', ({input, expected}) => { @@ -65,25 +65,39 @@ describe('[configuration-file] Methods', () => { expect(result).toBe(expected); }); - it.each` - input | expected - ${{key: 'test', description: 'Test description', defaultValue: 0}} | ${'# Test description\n# test: 0'} - ${{key: 'test', description: 'Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. ', defaultValue: 0}} | ${'# Test description. Test description. Test description. Test description. Test\n# description. Test description. Test description. Test description. Test\n# description. Test description. Test description.\n# test: 0'} - ${{key: 'test', description: 'Test description', defaultValue: 0, options: {select: [{text: 'Option1', value: 'option'},{text: 'Option2', value: 'option2'}]}}} | ${'# Test description Allowed values: option (Option1), option2 (Option2).\n# test: 0'} - ${{key: 'test', description: 'Test description', defaultValue: 0, options: {switch: {values: { disabled: {label: 'Enabled', value: 'disabled'}, enabled: {label: 'Enabled', value: 'enabled'}, }}}}} | ${'# Test description Allowed values: enabled (Enabled), disabled (Enabled).\n# test: 0'} - `('printSetting: input: $input , expected: $expected', ({input, expected}) => { + it.each( + [ + { + input: {key: 'test', description: 'Test description', defaultValue: 0}, + expected: '# Test description\n# test: 0' + }, + { + input: {key: 'test', description: 'Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. ', defaultValue: 0}, + expected: '# Test description. Test description. Test description. Test description. Test\n# description. Test description. Test description. Test description. Test\n# description. Test description. Test description.\n# test: 0' + }, + { + input: {key: 'test', description: 'Test description', defaultValue: 0, options: {select: [{text: 'Option1', value: 'option'},{text: 'Option2', value: 'option2'}]}}, + expected: '# Test description Allowed values: option (Option1), option2 (Option2).\n# test: 0' + }, + { + input: {key: 'test', description: 'Test description', defaultValue: 0, options: {switch: {values: { disabled: {label: 'Enabled', value: 'disabled'}, enabled: {label: 'Enabled', value: 'enabled'}, }}}}, + expected: '# Test description Allowed values: enabled (Enabled), disabled (Enabled).\n# test: 0' + } + + ] + )('printSetting: input: $input , expected: $expected', ({input, expected}) => { const result = printSetting(input); expect(result).toMatch(expected); }); it.each` - input | expected - ${4} | ${4} - ${''} | ${"''"} - ${'test'} | ${'test'} + input | expected + ${4} | ${4} + ${''} | ${"''"} + ${'test'} | ${'test'} ${{key: 'value'}} | ${'{\"key\":\"value\"}'} - ${[]} | ${"[]"} - ${''} | ${"''"} + ${[]} | ${"[]"} + ${''} | ${"''"} `('printSettingValue: input: $input , expected: $expected', ({input, expected}) => { expect(printSettingValue(input)).toBe(expected); }); diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts index 01c2982900..55d9ab493d 100644 --- a/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -131,8 +131,8 @@ hosts: }); it.each` - settings | responseStatusCode - ${{pattern: 'test-alerts-groupA-*'}} | ${200} + settings | responseStatusCode + ${{pattern: 'test-alerts-groupA-*'}} | ${200} ${{pattern: 'test-alerts-groupA-*','logs.level': 'debug'}} | ${200} `(`Update the plugin configuration: $settings. PUT /utils/configuration - $responseStatusCode`, async ({responseStatusCode, settings}) => { const response = await supertest(innerServer.listener) From 95b9020e4c1d1b238fdcd169532c581fa3524501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Wed, 5 Oct 2022 12:44:05 +0200 Subject: [PATCH 61/79] fix: remove unnecessary import --- server/lib/reporting/printer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index 3f6205edbf..b27db3947c 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -12,7 +12,6 @@ import * as TimSort from 'timsort'; import { getConfiguration } from '../get-configuration'; import { REPORTS_PRIMARY_COLOR} from '../../../common/constants'; import { getSettingDefaultValue } from '../../../common/services/settings'; -import { try } from 'bluebird'; const COLORS = { PRIMARY: REPORTS_PRIMARY_COLOR From 034bd3425bb7b6a7c49791ac5af6cf2c44ee54d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Wed, 5 Oct 2022 13:44:22 +0200 Subject: [PATCH 62/79] test(endpoints): add test to GET /reports endpoint --- server/routes/wazuh-reporting.test.ts | 54 +++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/server/routes/wazuh-reporting.test.ts b/server/routes/wazuh-reporting.test.ts index e338f7cb0e..0b175080be 100644 --- a/server/routes/wazuh-reporting.test.ts +++ b/server/routes/wazuh-reporting.test.ts @@ -9,15 +9,15 @@ import { WazuhUtilsRoutes } from './wazuh-utils'; import { WazuhReportingRoutes } from './wazuh-reporting'; import { WazuhUtilsCtrl } from '../controllers/wazuh-utils/wazuh-utils'; import md5 from 'md5'; - - +import path from 'path'; import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../lib/filesystem'; import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_DATA_CONFIG_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_ABSOLUTE_PATH + WAZUH_DATA_ABSOLUTE_PATH, + WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH } from '../../common/constants'; import { execSync } from 'child_process'; import fs from 'fs'; @@ -100,6 +100,54 @@ afterAll(async () => { execSync(`rm -rf ${WAZUH_DATA_ABSOLUTE_PATH}`); }); +describe('[endpoint] GET /reports', () => { + const directories = [ + { username: 'admin', files: 0 }, + { username: '../../etc', files: 1 }, + ]; + beforeAll(() => { + // Create /data/wazuh directory. + createDataDirectoryIfNotExists(); + + // Create /data/wazuh/config directory. + createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); + + // Create /data/wazuh/logs directory. + createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); + + // Create /data/wazuh/downloads directory. + createDirectoryIfNotExists(WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH); + + // Create /data/wazuh/downloads/reports directory. + createDirectoryIfNotExists(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH); + + // Create directories and file/s within directory. + directories.forEach(({ username, files }) => { + const hashUsername = md5(username); + createDirectoryIfNotExists(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, hashUsername)); + if (files) { + Array.from(Array(files).keys()).forEach(indexFile => { + console.log('Generating', username, indexFile) + fs.closeSync(fs.openSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, hashUsername, `report_${indexFile}.pdf`), 'w')); + }); + } + }); + }); + + afterAll(async () => { + // Remove /data/wazuh/downloads directory. + execSync(`rm -rf ${WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH}`); + }); + + it.each(directories)('get reports of $username. status response: $responseStatus', async ({ username, files }) => { + const response = await supertest(innerServer.listener) + .get(`/reports`) + .set('x-test-username', username) + .expect(200); + expect(response.body.reports).toHaveLength(files); + }); +}); + describe('[endpoint] PUT /utils/configuration', () => { beforeAll(() => { // Create the configuration file with custom content From 91e6749778926448353bcc8097270b63e54f355e Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 5 Oct 2022 15:23:21 +0200 Subject: [PATCH 63/79] Improved report unit test with more cases --- server/routes/wazuh-reporting.test.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/server/routes/wazuh-reporting.test.ts b/server/routes/wazuh-reporting.test.ts index 0b175080be..8b1b9b2ccd 100644 --- a/server/routes/wazuh-reporting.test.ts +++ b/server/routes/wazuh-reporting.test.ts @@ -149,7 +149,7 @@ describe('[endpoint] GET /reports', () => { }); describe('[endpoint] PUT /utils/configuration', () => { - beforeAll(() => { + beforeEach(() => { // Create the configuration file with custom content const fileContent = `--- pattern: test-alerts-* @@ -166,20 +166,24 @@ describe('[endpoint] PUT /utils/configuration', () => { fs.writeFileSync(WAZUH_DATA_CONFIG_APP_PATH, fileContent, 'utf8'); }); - afterAll(() => { + afterEach(() => { // Remove the configuration file fs.unlinkSync(WAZUH_DATA_CONFIG_APP_PATH); }); // expectedMD5 variable is a verified md5 of a report generated with this header and footer - // if any of the parameters is changed this variable should be updated with the new md5 + // If any of the parameters is changed this variable should be updated with the new md5 it.each` - footer | header | responseStatusCode | expectedMD5 - ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'}| ${200} | ${'0acbd4ee321699791b080b45c11dfe2b'} -`(`Set custom report header and footer - Verify PDF output`, async ({footer, header, responseStatusCode, expectedMD5}) => { + footer | header | responseStatusCode | expectedMD5 | tab + ${null} | ${null} | ${200} | ${'1bdc0cc05cc79fdfbb9b734a4e1cc07b'} | ${'pm'} + ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'}| ${200} | ${'0acbd4ee321699791b080b45c11dfe2b'} | ${'general'} + ${''} | ${''} | ${200} | ${'5f9e16540e9d8109bed75b2a8f825164'} | ${'fim'} + ${'Custom Footer'} | ${null} | ${200} | ${'5bd4c559419028fd0d282c5e8408ecff'} | ${'aws'} + ${null} | ${'Custom Header'} | ${200} | ${'f6bfca395bc3fc78105f66120f336443'} | ${'gcp'} +`(`Set custom report header and footer - Verify PDF output`, async ({footer, header, responseStatusCode, expectedMD5, tab}) => { // Mock PDF report parameters - const reportBody = { "array": [], "filters": [], "time": { "from": '2022-10-01T09:59:40.825Z', "to": '2022-10-04T09:59:40.825Z' }, "searchBar": "", "tables": [], "tab": "general", "section": "overview", "agents": false, "browserTimezone": "Europe/Madrid", "indexPatternTitle": "wazuh-alerts-*", "apiId": "default" }; + const reportBody = { "array": [], "filters": [], "time": { "from": '2022-10-01T09:59:40.825Z', "to": '2022-10-04T09:59:40.825Z' }, "searchBar": "", "tables": [], "tab": tab, "section": "overview", "agents": false, "browserTimezone": "Europe/Madrid", "indexPatternTitle": "wazuh-alerts-*", "apiId": "default" }; // Define custom configuration const configurationBody = {}; @@ -206,8 +210,9 @@ describe('[endpoint] PUT /utils/configuration', () => { } } + // Generate PDF report const responseReport = await supertest(innerServer.listener) - .post(`/reports/modules/general`) + .post(`/reports/modules/${tab}`) .set('x-test-username', USER_NAME) .send(reportBody) .expect(200); From 282b7e9680511750fe5dff3bd2606d3d64aa0ae4 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 5 Oct 2022 16:16:28 +0200 Subject: [PATCH 64/79] Fix small typo --- common/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.ts b/common/constants.ts index 29cf6c01ea..e879f2d026 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -799,7 +799,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, }, "customization.logo.reports": { - title: "PDF reports Logo", + title: "PDF reports logo", description: `This logo is used in the PDF reports generated by the app. It's placed at the top left corner of every page of the PDF.`, category: SettingCategory.CUSTOMIZATION, type: EpluginSettingType.text, From 04f74577910f9c06f98544e9d5ee5b1176b5df82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Thu, 6 Oct 2022 11:43:38 +0200 Subject: [PATCH 65/79] fix(settings): fix a typo in a toast related to modify the plugin settings from UI --- public/components/settings/configuration/configuration.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index 5a6b7424c8..fdc9721230 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -231,7 +231,7 @@ export const WzConfigurationSettings = compose ( const toastRequiresReloadingBrowserTab = () => { getToasts().add({ color: 'success', - title: 'This setting require you to reload the page to take effect.', + title: 'This setting requires you to reload the page to take effect.', text: window.location.reload()} size="s">Reload page From 312461c9da77c54ef84a330125bdf879bca9c8c5 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Tue, 11 Oct 2022 13:48:32 +0200 Subject: [PATCH 66/79] Changed Custom Branding documentation link --- common/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.ts b/common/constants.ts index e879f2d026..82caaa9fc8 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -459,7 +459,7 @@ export const PLUGIN_SETTINGS_CATEGORIES: { [category: number]: TPluginSettingCat [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', description: "If you want to use custom branding elements such as logos, you can do so by editing the settings below.", - documentationLink: 'user-manual/wazuh-dashboard/config-file.html#logo-customization', + documentationLink: 'user-manual/wazuh-dashboard/white-labeling.html', renderOrder: SettingCategory.CUSTOMIZATION, } }; From 6d5afcd9a0266998393ef8b24dc32c327f4f815f Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Tue, 11 Oct 2022 16:33:56 +0200 Subject: [PATCH 67/79] Merge centralize plugin settings PR --- common/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.ts b/common/constants.ts index 55f9a9ece4..5947b5f283 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -464,7 +464,7 @@ export const PLUGIN_SETTINGS_CATEGORIES: { [category: number]: TPluginSettingCat [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', description: "If you want to use custom branding elements such as logos, you can do so by editing the settings below.", - documentationLink: 'user-manual/wazuh-dashboard/config-file.html#logo-customization', + documentationLink: 'user-manual/wazuh-dashboard/white-labeling.html', renderOrder: SettingCategory.CUSTOMIZATION, } }; From cb793d64838aed7e19093a8874b15e03776a5c1e Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Tue, 11 Oct 2022 16:44:49 +0200 Subject: [PATCH 68/79] Fix white-labeling documentation link --- common/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index eb628e5331..cfad5c9d57 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -417,7 +417,7 @@ export type TPluginSetting = { 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 uiFormTransformInputValueToConfigurationValue?: (value: any) => any - // Validate the value in the form of Settings/Configuration. It returns a string if there is some validation error. + // Validate the value in the form of Settings/Configuration. It returns a string if there is some validation error. validate?: (value: any) => string | undefined // Validate function creator to validate the setting in the backend. It uses `schema` of the `@kbn/config-schema` package. validateBackend?: (schema: any) => (value: unknown) => string | undefined @@ -464,7 +464,7 @@ export const PLUGIN_SETTINGS_CATEGORIES: { [category: number]: TPluginSettingCat [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', description: "If you want to use custom branding elements such as logos, you can do so by editing the settings below.", - documentationLink: 'user-manual/wazuh-dashboard/config-file.html#logo-customization', + documentationLink: 'user-manual/wazuh-dashboard/white-labeling.html', renderOrder: SettingCategory.CUSTOMIZATION, } }; From d4c18b182b6e1b62f9d91b5875e457667c58ae18 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Thu, 20 Oct 2022 10:22:19 +0200 Subject: [PATCH 69/79] Code format --- public/components/common/form/input_text_area.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/components/common/form/input_text_area.tsx b/public/components/common/form/input_text_area.tsx index e41fbb45f8..67c45127d5 100644 --- a/public/components/common/form/input_text_area.tsx +++ b/public/components/common/form/input_text_area.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - EuiTextArea, -} from '@elastic/eui'; +import { EuiTextArea } from '@elastic/eui'; import { IInputFormType } from './types'; export const InputFormTextArea = ({ value, isInvalid, onChange } : IInputFormType) => { From 19c7cacffdaee8c207992406679a1abc1abca5c3 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Thu, 20 Oct 2022 10:53:45 +0200 Subject: [PATCH 70/79] Delete unused imports --- common/services/settings.ts | 15 +++++++-------- public/components/common/form/hooks.tsx | 4 ++-- public/components/common/form/index.tsx | 10 +++------- public/components/common/form/input_editor.tsx | 4 +--- public/components/common/form/input_number.tsx | 4 +--- public/components/common/form/input_select.tsx | 4 +--- public/components/common/form/input_switch.tsx | 4 +--- public/components/common/form/input_text.tsx | 4 +--- .../settings/configuration/configuration.tsx | 7 +++---- 9 files changed, 20 insertions(+), 36 deletions(-) diff --git a/common/services/settings.ts b/common/services/settings.ts index f01eea8647..4e88c6c5fe 100644 --- a/common/services/settings.ts +++ b/common/services/settings.ts @@ -1,5 +1,4 @@ import { - EpluginSettingType, PLUGIN_SETTINGS, PLUGIN_SETTINGS_CATEGORIES, TPluginSetting, @@ -78,8 +77,8 @@ const formatSettingValueToFileType = { /** * Group the settings by category - * @param settings - * @returns + * @param settings + * @returns */ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ const settingsSortedByCategories = settings @@ -99,8 +98,8 @@ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ /** * Get the plugin setting description composed. - * @param options - * @returns + * @param options + * @returns */ export function getPluginSettingDescription({description, options}: TPluginSetting): string{ return [ @@ -114,9 +113,9 @@ export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ /** * Format the pair value-label to display the pair. If label and the string of value are equals, only displays the value, if not, displays both. - * @param value - * @param label - * @returns + * @param value + * @param label + * @returns */ export function formatLabelValuePair(label, value){ return label !== `${value}` diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx index 8bebee8102..1778f147d7 100644 --- a/public/components/common/form/hooks.tsx +++ b/public/components/common/form/hooks.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import _ from 'lodash'; +import { isEqual } from 'lodash'; import { EpluginSettingType } from '../../../../common/constants'; function getValueFromEvent(event, type){ @@ -28,7 +28,7 @@ export const useForm = (fields) => { ...restFieldState, type: fields[fieldKey].type, value, - changed: !_.isEqual(restFieldState.initialValue, value), + changed: !isEqual(restFieldState.initialValue, value), error: fields[fieldKey]?.validate?.(restFieldState.currentValue), onChange: (event) => { const inputValue = getValueFromEvent(event, fields[fieldKey].type); diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx index 0681a4c6b6..62d8e15080 100644 --- a/public/components/common/form/index.tsx +++ b/public/components/common/form/index.tsx @@ -1,14 +1,10 @@ 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 { - EuiFormRow, -} from '@elastic/eui'; +import { EuiFormRow } from '@elastic/eui'; export const InputForm = ({ type, value, onChange, error, label, preInput, postInput, ...rest}) => { @@ -38,9 +34,9 @@ export const InputForm = ({ type, value, onChange, error, label, preInput, postI {typeof postInput === 'function' ? postInput({value, error}) : postInput} ) - : input; + : input; -}; +}; const Input = { switch: InputFormSwitch, diff --git a/public/components/common/form/input_editor.tsx b/public/components/common/form/input_editor.tsx index 41002090ca..ab38c89f6d 100644 --- a/public/components/common/form/input_editor.tsx +++ b/public/components/common/form/input_editor.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - EuiCodeEditor, -} from '@elastic/eui'; +import { EuiCodeEditor } from '@elastic/eui'; import { IInputFormType } from './types'; export const InputFormEditor = ({options, value, onChange}: IInputFormType) => { diff --git a/public/components/common/form/input_number.tsx b/public/components/common/form/input_number.tsx index 479661b27c..acc21002f4 100644 --- a/public/components/common/form/input_number.tsx +++ b/public/components/common/form/input_number.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - EuiFieldNumber, -} from '@elastic/eui'; +import { EuiFieldNumber } from '@elastic/eui'; import { IInputFormType } from './types'; export const InputFormNumber = ({ options, value, onChange }: IInputFormType) => { diff --git a/public/components/common/form/input_select.tsx b/public/components/common/form/input_select.tsx index 1da3db74bf..a8f02e99d7 100644 --- a/public/components/common/form/input_select.tsx +++ b/public/components/common/form/input_select.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - EuiSelect, -} from '@elastic/eui'; +import { EuiSelect } from '@elastic/eui'; import { IInputFormType } from './types'; export const InputFormSelect = ({ options, value, onChange }: IInputFormType) => { diff --git a/public/components/common/form/input_switch.tsx b/public/components/common/form/input_switch.tsx index 6292405298..75a575d97f 100644 --- a/public/components/common/form/input_switch.tsx +++ b/public/components/common/form/input_switch.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - EuiSwitch, -} from '@elastic/eui'; +import { EuiSwitch } from '@elastic/eui'; import { IInputFormType } from './types'; export const InputFormSwitch = ({ options, value, onChange }: IInputFormType) => { diff --git a/public/components/common/form/input_text.tsx b/public/components/common/form/input_text.tsx index 994ac62452..feb0d218ee 100644 --- a/public/components/common/form/input_text.tsx +++ b/public/components/common/form/input_text.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - EuiFieldText, -} from '@elastic/eui'; +import { EuiFieldText } from '@elastic/eui'; import { IInputFormType } from "./types"; export const InputFormText = ({ value, isInvalid, onChange }: IInputFormType) => { diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx index fdc9721230..b8ccba3f2b 100644 --- a/public/components/settings/configuration/configuration.tsx +++ b/public/components/settings/configuration/configuration.tsx @@ -29,10 +29,9 @@ import { import store from '../../../redux/store' import { updateSelectedSettingsSection } from '../../../redux/actions/appStateActions'; 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 { PLUGIN_PLATFORM_NAME, PLUGIN_SETTINGS, PLUGIN_SETTINGS_CATEGORIES, UI_LOGGER_LEVELS, WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../../common/constants'; import { compose } from 'redux'; import { getSettingsDefaultList, groupSettingsByCategory, getCategorySettingByTitle } 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'; @@ -69,7 +68,7 @@ const trasnsfromPluginSettingsToFormFields = configuration => Object.fromEntries uiFormTransformInputValueToConfigurationValue, ...rest }) => ([ - key, + key, { type, validate: validate?.bind?.(rest), @@ -138,7 +137,7 @@ const WzConfigurationSettingsProvider = (props) => { settingsToUpdate.saveOnConfigurationFile )); }; - const responses = await Promise.all(requests); + const responses = await Promise.all(requests); // Show the toasts if necessary responses.some(({data: { data: {requiresRunningHealthCheck}}}) => requiresRunningHealthCheck) && toastRequiresRunningHealthcheck(); From e6da4c3abfae2c9501baf676f175d33cea4bfe48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Fri, 21 Oct 2022 10:35:29 +0200 Subject: [PATCH 71/79] fix(settings): fix a problem with the useForm hook --- public/components/common/form/hooks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx index 1778f147d7..0a88a24687 100644 --- a/public/components/common/form/hooks.tsx +++ b/public/components/common/form/hooks.tsx @@ -29,7 +29,7 @@ export const useForm = (fields) => { type: fields[fieldKey].type, value, changed: !isEqual(restFieldState.initialValue, value), - error: fields[fieldKey]?.validate?.(restFieldState.currentValue), + error: fields[fieldKey]?.validate?.(value), onChange: (event) => { const inputValue = getValueFromEvent(event, fields[fieldKey].type); const currentValue = fields[fieldKey]?.transformChangedInputValue?.(inputValue) ?? inputValue; From dffeb099918d34212f70e7936b51036835344bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Fri, 21 Oct 2022 10:36:53 +0200 Subject: [PATCH 72/79] fix(settings): refactor the settings validation function to a class and rename the file --- common/constants.ts | 188 ++++++++++++------------- common/services/settings-validate.ts | 85 ------------ common/services/settings-validator.ts | 192 ++++++++++++++++++++++++++ 3 files changed, 286 insertions(+), 179 deletions(-) delete mode 100644 common/services/settings-validate.ts create mode 100644 common/services/settings-validator.ts diff --git a/common/constants.ts b/common/constants.ts index 5947b5f283..146212e653 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -12,7 +12,7 @@ import path from 'path'; import { version } from '../package.json'; import { validate as validateNodeCronInterval } from 'node-cron'; -import { composeValidate, validateBooleanIs, validateLiteral, validateNumber, validateStringNoInvalidCharacters, validateStringNoEmpty, validateStringNoLiteral, validateStringNoSpaces, validateStringNoStartWith, validateJSON, validateObjectArray, validateStringIs } from './services/settings-validate'; +import { SettingsValidator } from '../common/services/settings-validator'; // Plugin export const PLUGIN_VERSION = version; @@ -480,11 +480,11 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresRunningHealthCheck: true, // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') ), validateBackend: function(schema){ return schema.string({validate: this.validate}); @@ -509,7 +509,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -533,7 +533,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -557,7 +557,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -581,7 +581,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -605,7 +605,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -629,7 +629,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -653,7 +653,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -677,7 +677,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -691,11 +691,11 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromFile: true, isConfigurableFromUI: true, // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') ), validateBackend: function(schema){ return schema.string({validate: this.validate}); @@ -724,17 +724,17 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return value; }; }, - validate: validateJSON(composeValidate( - validateObjectArray(composeValidate( - validateStringIs, - validateStringNoEmpty, - validateStringNoSpaces, + validate: SettingsValidator.json(SettingsValidator.compose( + SettingsValidator.array(SettingsValidator.compose( + SettingsValidator.isString, + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, )), )), validateBackend: function(schema){ - return schema.arrayOf(schema.string({validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, + return schema.arrayOf(schema.string({validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, )})); }, }, @@ -768,7 +768,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresRunningHealthCheck: true, validate: function (value){ - return validateLiteral(this.options.select.map(({value}) => value))(value) + return SettingsValidator.literal(this.options.select.map(({value}) => value))(value) }, validateBackend: function(schema){ return schema.oneOf(this.options.select.map(({value}) => schema.literal(value))); @@ -784,11 +784,11 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresRunningHealthCheck: true, // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#', '*') ), validateBackend: function(schema){ return schema.string({validate: this.validate}); @@ -815,7 +815,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return Number(value); }, validate: function(value){ - return validateNumber(this.options.number)(value) + return SettingsValidator.number(this.options.number)(value) }, validateBackend: function(schema){ return schema.number({validate: this.validate.bind(this)}); @@ -842,7 +842,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return Number(value); }, validate: function(value){ - return validateNumber(this.options.number)(value) + return SettingsValidator.number(this.options.number)(value) }, validateBackend: function(schema){ return schema.number({validate: this.validate.bind(this)}); @@ -883,7 +883,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -949,17 +949,17 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return value; }; }, - validate: validateJSON(composeValidate( - validateObjectArray(composeValidate( - validateStringIs, - validateStringNoEmpty, - validateStringNoSpaces, + validate: SettingsValidator.json(SettingsValidator.compose( + SettingsValidator.array(SettingsValidator.compose( + SettingsValidator.isString, + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, )), )), validateBackend: function(schema){ - return schema.arrayOf(schema.string({validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, + return schema.arrayOf(schema.string({validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, )})); }, }, @@ -971,7 +971,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { defaultValue: "", isConfigurableFromFile: true, isConfigurableFromUI: true, - validate: validateStringNoSpaces, + validate: SettingsValidator.hasNoSpaces, validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -984,7 +984,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { defaultValue: "", isConfigurableFromFile: true, isConfigurableFromUI: false, - validate: validateStringNoEmpty, + validate: SettingsValidator.isNotEmptyString, validateBackend: function(schema){ return schema.string({validate: this.validate}); }, @@ -1008,7 +1008,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1032,7 +1032,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1056,7 +1056,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1080,7 +1080,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1104,7 +1104,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1128,7 +1128,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1152,7 +1152,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1176,7 +1176,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1200,7 +1200,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1224,7 +1224,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1248,7 +1248,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1272,7 +1272,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1296,7 +1296,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1321,7 +1321,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1350,23 +1350,23 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { }; }, // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: validateJSON(composeValidate( - validateObjectArray(composeValidate( - validateStringIs, - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoLiteral('.', '..'), - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + validate: SettingsValidator.json(SettingsValidator.compose( + SettingsValidator.array(SettingsValidator.compose( + SettingsValidator.isString, + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noLiteralString('.', '..'), + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') )), )), validateBackend: function(schema){ - return schema.arrayOf(schema.string({validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoLiteral('.', '..'), - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + return schema.arrayOf(schema.string({validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noLiteralString('.', '..'), + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') )})); }, }, @@ -1389,7 +1389,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1416,7 +1416,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresRestartingPluginPlatform: true, validate: function (value){ - return validateLiteral(this.options.select.map(({value}) => value))(value) + return SettingsValidator.literal(this.options.select.map(({value}) => value))(value) }, validateBackend: function(schema){ return schema.oneOf(this.options.select.map(({value}) => schema.literal(value))); @@ -1432,12 +1432,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresRunningHealthCheck: true, // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoLiteral('.', '..'), - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noLiteralString('.', '..'), + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') ), validateBackend: function(schema){ return schema.string({validate: this.validate}); @@ -1463,7 +1463,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return Number(value); }, validate: function(value){ - return validateNumber(this.options.number)(value); + return SettingsValidator.number(this.options.number)(value); }, validateBackend: function(schema){ return schema.number({validate: this.validate.bind(this)}); @@ -1499,7 +1499,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, requiresRunningHealthCheck: true, validate: function (value){ - return validateLiteral(this.options.select.map(({value}) => value))(value) + return SettingsValidator.literal(this.options.select.map(({value}) => value))(value) }, validateBackend: function(schema){ return schema.oneOf(this.options.select.map(({value}) => schema.literal(value))); @@ -1525,7 +1525,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { uiFormTransformChangedInputValue: function (value: boolean | string): boolean { return Boolean(value); }, - validate: validateBooleanIs, + validate: SettingsValidator.isBoolean, validateBackend: function(schema){ return schema.boolean(); }, @@ -1551,7 +1551,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return Number(value); }, validate: function(value){ - return validateNumber(this.options.number)(value); + return SettingsValidator.number(this.options.number)(value); }, validateBackend: function(schema){ return schema.number({validate: this.validate.bind(this)}); @@ -1566,12 +1566,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromFile: true, isConfigurableFromUI: true, requiresRunningHealthCheck: true, - validate: composeValidate( - validateStringNoEmpty, - validateStringNoSpaces, - validateStringNoLiteral('.', '..'), - validateStringNoStartWith('-', '_', '+', '.'), - validateStringNoInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noLiteralString('.', '..'), + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters('\\', '/', '?', '"', '<', '>', '|', ',', '#') ), validateBackend: function(schema){ return schema.string({minLength: 1, validate: this.validate}); @@ -1598,7 +1598,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return Number(value); }, validate: function(value){ - return validateNumber(this.options.number)(value); + return SettingsValidator.number(this.options.number)(value); }, validateBackend: function(schema){ return schema.number({validate: this.validate.bind(this)}); @@ -1625,7 +1625,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return Number(value); }, validate: function(value){ - return validateNumber(this.options.number)(value); + return SettingsValidator.number(this.options.number)(value); }, validateBackend: function(schema){ return schema.number({validate: this.validate.bind(this)}); diff --git a/common/services/settings-validate.ts b/common/services/settings-validate.ts deleted file mode 100644 index 92947d64fa..0000000000 --- a/common/services/settings-validate.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Utils -export const composeValidate = (...functions) => value => { - for(const fn of functions){ - const result = fn(value); - if(typeof result === 'string' && result.length > 0){ - return result; - }; - }; -}; - -// String -export const validateStringIs = (value: unknown): string | undefined => typeof value === 'string' ? undefined : "Value is not a string."; -export const validateStringNoSpaces = (value: string): string | undefined => /^\S*$/.test(value) ? undefined : "It can't contain spaces."; -export const validateStringNoEmpty = (value: string): string | undefined => { - if(typeof value === 'string'){ - if(value.length === 0){ - return "Value can not be empty." - }else{ - return undefined; - } - }; -}; -export const validateStringMultipleLines = (options: {min?: number, max?: number} = {}) => (value: number) => { - const lines = value.split(/\r\n|\r|\n/).length; - if(typeof options.min !== 'undefined' && lines < options.min){ - return `The string should have more or ${options.min} line/s.`; - }; - if(typeof options.max !== 'undefined' && lines > options.max){ - return `The string should have less or ${options.max} line/s.`; - }; -}; - -export const validateStringNoInvalidCharacters = (...invalidCharacters: string[]) => (value: string): string | undefined => invalidCharacters.some(invalidCharacter => value.includes(invalidCharacter)) ? `It can't contain invalid characters: ${invalidCharacters.join(', ')}.` : undefined; - -export const validateStringNoStartWith = (...invalidStartingCharacters: string[]) => (value: string): string | undefined => invalidStartingCharacters.some(invalidStartingCharacter => value.startsWith(invalidStartingCharacter)) ? `It can't start with: ${invalidStartingCharacters.join(', ')}.` : undefined; - -export const validateStringNoLiteral = (...invalidLiterals: string[]) => (value: string): string | undefined => invalidLiterals.some(invalidLiteral => value === invalidLiteral) ? `It can't be: ${invalidLiterals.join(', ')}.` : undefined; - -// Boolean -export const validateBooleanIs = (value: string): string | undefined => typeof value === 'boolean' ? undefined : "It should be a boolean. Allowed values: true or false."; - -// Number -export const validateNumber = (options: {min?: number, max?: number} = {}) => (value: number) => { - if(typeof options.min !== 'undefined' && value < options.min){ - return `Value should be greater or equal than ${options.min}.`; - }; - if(typeof options.max !== 'undefined' && value > options.max){ - return `Value should be lower or equal than ${options.max}.`; - }; -}; - -// Complex -export const validateJSON = (validateParsed: (object: any) => string | undefined) => (value: string) => { - let jsonObject; - // Try to parse the string as JSON - try{ - jsonObject = JSON.parse(value); - }catch(error){ - return "Value can't be parsed. There is some error."; - }; - - return validateParsed ? validateParsed(jsonObject) : undefined; -}; - -export const validateObjectArray = (validationElement: (json: any) => string | undefined) => (value: unknown[]) => { - // Check the JSON is an array - if(!Array.isArray(value)){ - return 'Value is not a valid list.'; - }; - - return validationElement ? value.reduce((accum, elementValue) => { - if(accum){ - return accum; - }; - - const resultValidationElement = validationElement(elementValue); - if(resultValidationElement){ - return resultValidationElement; - }; - - return accum; - }, undefined) : undefined; -}; - -export const validateLiteral = (literals) => (value: any): string | undefined => literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}.`; diff --git a/common/services/settings-validator.ts b/common/services/settings-validator.ts new file mode 100644 index 0000000000..2d2398c8ec --- /dev/null +++ b/common/services/settings-validator.ts @@ -0,0 +1,192 @@ +export class SettingsValidator{ + /** + * Create a function that is a composition of the input validations + * @param functions SettingsValidator functions to compose + * @returns composed validation + */ + static compose(...functions) { + return function composedValidation(value) { + for (const fn of functions) { + const result = fn(value); + if (typeof result === 'string' && result.length > 0) { + return result; + }; + }; + }; + }; + + /** + * Check the value is a string + * @param value + * @returns + */ + static isString(value: unknown): string | undefined { + return typeof value === 'string' ? undefined : "Value is not a string."; + }; + + /** + * Check the string has no spaces + * @param value + * @returns + */ + static hasNoSpaces(value: string): string | undefined { + return /^\S*$/.test(value) ? undefined : "It can't contain spaces."; + }; + + /** + * Check the string has no empty + * @param value + * @returns + */ + static isNotEmptyString(value: string): string | undefined { + if (typeof value === 'string') { + if (value.length === 0) { + return "Value can not be empty." + } else { + return undefined; + } + }; + }; + + /** + * Check the number of string lines is limited + * @param options + * @returns + */ + static multipleLinesString(options: { min?: number, max?: number } = {}){ + return function (value: number){ + const lines = value.split(/\r\n|\r|\n/).length; + if (typeof options.min !== 'undefined' && lines < options.min) { + return `The string should have more or ${options.min} line/s.`; + }; + if (typeof options.max !== 'undefined' && lines > options.max) { + return `The string should have less or ${options.max} line/s.`; + }; + } + }; + + /** + * Creates a function that checks the string does not contain some characters + * @param invalidCharacters + * @returns + */ + static hasNotInvalidCharacters(...invalidCharacters: string[]){ + return function (value: string): string | undefined { + return invalidCharacters.some(invalidCharacter => value.includes(invalidCharacter)) + ? `It can't contain invalid characters: ${invalidCharacters.join(', ')}.` + : undefined; + }; + }; + + /** + * Creates a function that checks the string does not start with a substring + * @param invalidStartingCharacters + * @returns + */ + static noStartsWithString(...invalidStartingCharacters: string[]) { + return function (value: string): string | undefined { + return invalidStartingCharacters.some(invalidStartingCharacter => value.startsWith(invalidStartingCharacter)) + ? `It can't start with: ${invalidStartingCharacters.join(', ')}.` + : undefined; + }; + }; + + /** + * Creates a function that checks the string is not equals to some values + * @param invalidLiterals + * @returns + */ + static noLiteralString(...invalidLiterals: string[]){ + return function (value: string): string | undefined { + return invalidLiterals.some(invalidLiteral => value === invalidLiteral) + ? `It can't be: ${invalidLiterals.join(', ')}.` + : undefined; + }; + }; + + /** + * Check the value is a boolean + * @param value + * @returns + */ + static isBoolean(value: string): string | undefined { + return typeof value === 'boolean' + ? undefined + : "It should be a boolean. Allowed values: true or false."; + }; + + /** + * Check the value is a number between some optional limits + * @param options + * @returns + */ + static number(options: { min?: number, max?: number } = {}) { + return function (value: number){ + if (typeof options.min !== 'undefined' && value < options.min) { + return `Value should be greater or equal than ${options.min}.`; + }; + if (typeof options.max !== 'undefined' && value > options.max) { + return `Value should be lower or equal than ${options.max}.`; + }; + }; + }; + + /** + * Creates a function that checks if the value is a json + * @param validateParsed Optional parameter to validate the parsed object + * @returns + */ + static json(validateParsed: (object: any) => string | undefined) { + return function (value: string){ + let jsonObject; + // Try to parse the string as JSON + try { + jsonObject = JSON.parse(value); + } catch (error) { + return "Value can't be parsed. There is some error."; + }; + + return validateParsed ? validateParsed(jsonObject) : undefined; + }; + }; + + /** + * Creates a function that checks is the value is an array and optionally validates each element + * @param validationElement Optional function to validate each element of the array + * @returns + */ + static array(validationElement: (json: any) => string | undefined) { + return function(value: unknown[]) { + // Check the JSON is an array + if (!Array.isArray(value)) { + return 'Value is not a valid list.'; + }; + + return validationElement + ? value.reduce((accum, elementValue) => { + if (accum) { + return accum; + }; + + const resultValidationElement = validationElement(elementValue); + if (resultValidationElement) { + return resultValidationElement; + }; + + return accum; + }, undefined) + : undefined; + }; + }; + + /** + * Creates a function that checks if the value is equal to list of values + * @param literals Array of values to compare + * @returns + */ + static literal(literals: unknown[]){ + return function(value: any): string | undefined{ + return literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}.`; + }; + }; +}; From e5a28eab408142389fec1f205265fcaf7e207c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Fri, 21 Oct 2022 12:10:06 +0200 Subject: [PATCH 73/79] feat(settings): add check for integer numbers and adapt the affected settings --- common/constants.ts | 19 +++++++++++++------ common/plugin-settings.test.ts | 12 ++++++++++++ common/services/settings-validator.ts | 19 +++++++++++++++---- server/routes/wazuh-utils/wazuh-utils.test.ts | 12 +++++++++--- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 146212e653..6cbb824278 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -359,6 +359,7 @@ type TPluginSettingOptionsNumber = { number: { min?: number max?: number + integer?: boolean } }; @@ -805,7 +806,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { requiresRunningHealthCheck: true, options: { number: { - min: 0 + min: 0, + integer: true } }, uiFormTransformConfigurationValueToInputValue: function(value: number): string { @@ -832,7 +834,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { requiresRunningHealthCheck: true, options: { number: { - min: 1 + min: 1, + integer: true } }, uiFormTransformConfigurationValueToInputValue: function(value: number){ @@ -1453,7 +1456,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { isConfigurableFromUI: true, options: { number: { - min: 1500 + min: 1500, + integer: true } }, uiFormTransformConfigurationValueToInputValue: function(value: number){ @@ -1541,7 +1545,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { requiresRestartingPluginPlatform: true, options: { number: { - min: 60 + min: 60, + integer: true } }, uiFormTransformConfigurationValueToInputValue: function(value: number){ @@ -1588,7 +1593,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { requiresRunningHealthCheck: true, options: { number: { - min: 0 + min: 0, + integer: true } }, uiFormTransformConfigurationValueToInputValue: function(value: number){ @@ -1615,7 +1621,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { requiresRunningHealthCheck: true, options: { number: { - min: 1 + min: 1, + integer: true } }, uiFormTransformConfigurationValueToInputValue: function(value: number){ diff --git a/common/plugin-settings.test.ts b/common/plugin-settings.test.ts index 35d636a545..e436be7336 100644 --- a/common/plugin-settings.test.ts +++ b/common/plugin-settings.test.ts @@ -81,8 +81,12 @@ describe('[settings] Input validation', () => { ${'cron.statistics.index.name'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} ${'cron.statistics.index.replicas'} | ${0} | ${undefined} ${'cron.statistics.index.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} + ${'cron.statistics.index.replicas'} | ${'1.2'} | ${'Number should be an integer.'} + ${'cron.statistics.index.replicas'} | ${1.2} | ${'Number should be an integer.'} ${'cron.statistics.index.shards'} | ${1} | ${undefined} ${'cron.statistics.index.shards'} | ${-1} | ${"Value should be greater or equal than 1."} + ${'cron.statistics.index.shards'} | ${'1.2'} | ${'Number should be an integer.'} + ${'cron.statistics.index.shards'} | ${1.2} | ${'Number should be an integer.'} ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${undefined} ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${undefined} ${'cron.statistics.interval'} | ${'custom'} | ${"Interval is not valid."} @@ -167,6 +171,8 @@ describe('[settings] Input validation', () => { ${'timeout'} | ${15000} | ${undefined} ${'timeout'} | ${1000} | ${'Value should be greater or equal than 1500.'} ${'timeout'} | ${''} | ${'Value should be greater or equal than 1500.'} + ${'timeout'} | ${'1.2'} | ${'Number should be an integer.'} + ${'timeout'} | ${1.2} | ${'Number should be an integer.'} ${'wazuh.monitoring.creation'} | ${'h'} | ${undefined} ${'wazuh.monitoring.creation'} | ${'d'} | ${undefined} ${'wazuh.monitoring.creation'} | ${'w'} | ${undefined} @@ -175,6 +181,8 @@ describe('[settings] Input validation', () => { ${'wazuh.monitoring.enabled'} | ${true} | ${undefined} ${'wazuh.monitoring.frequency'} | ${100} | ${undefined} ${'wazuh.monitoring.frequency'} | ${40} | ${"Value should be greater or equal than 60."} + ${'wazuh.monitoring.frequency'} | ${'1.2'} | ${'Number should be an integer.'} + ${'wazuh.monitoring.frequency'} | ${1.2} | ${'Number should be an integer.'} ${'wazuh.monitoring.pattern'} | ${'test'} | ${undefined} ${'wazuh.monitoring.pattern'} | ${'test*'} | ${undefined} ${'wazuh.monitoring.pattern'} | ${''} | ${'Value can not be empty.'} @@ -193,8 +201,12 @@ describe('[settings] Input validation', () => { ${'wazuh.monitoring.pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'wazuh.monitoring.replicas'} | ${0} | ${undefined} ${'wazuh.monitoring.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} + ${'wazuh.monitoring.replicas'} | ${'1.2'} | ${'Number should be an integer.'} + ${'wazuh.monitoring.replicas'} | ${1.2} | ${'Number should be an integer.'} ${'wazuh.monitoring.shards'} | ${1} | ${undefined} ${'wazuh.monitoring.shards'} | ${-1} | ${"Value should be greater or equal than 1."} + ${'wazuh.monitoring.shards'} | ${'1.2'} | ${'Number should be an integer.'} + ${'wazuh.monitoring.shards'} | ${1.2} | ${'Number should be an integer.'} `('$setting | $value | $expectedValidation', ({ setting, value, expectedValidation }) => { expect( PLUGIN_SETTINGS[setting].validate( diff --git a/common/services/settings-validator.ts b/common/services/settings-validator.ts index 2d2398c8ec..3cff47ccc2 100644 --- a/common/services/settings-validator.ts +++ b/common/services/settings-validator.ts @@ -120,14 +120,25 @@ export class SettingsValidator{ * @param options * @returns */ - static number(options: { min?: number, max?: number } = {}) { + static number(options: { min?: number, max?: number, integer?: boolean } = {}) { return function (value: number){ - if (typeof options.min !== 'undefined' && value < options.min) { + if (options.integer + && ( + (typeof value === 'string' ? ['.', ','].some(character => value.includes(character)) : false) + || !Number.isInteger(Number(value)) + ) + ) { + return 'Number should be an integer.' + }; + + const valueNumber = typeof value === 'string' ? Number(value) : value; + + if (typeof options.min !== 'undefined' && valueNumber < options.min) { return `Value should be greater or equal than ${options.min}.`; }; - if (typeof options.max !== 'undefined' && value > options.max) { + if (typeof options.max !== 'undefined' && valueNumber > options.max) { return `Value should be lower or equal than ${options.max}.`; - }; + }; }; }; diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts index 03ebc55f4a..e5b0352f30 100644 --- a/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -264,8 +264,10 @@ hosts: ${'cron.statistics.index.replicas'} | ${0} | ${200} | ${null} ${'cron.statistics.index.replicas'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.replicas]: Value should be greater or equal than 0."} ${'cron.statistics.index.replicas'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.index.replicas]: expected value of type [number] but got [string]"} + ${'cron.statistics.index.replicas'} | ${1.2} | ${400} | ${"[request body.cron.statistics.index.replicas]: Number should be an integer."} ${'cron.statistics.index.shards'} | ${1} | ${200} | ${null} ${'cron.statistics.index.shards'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.shards]: Value should be greater or equal than 1."} + ${'cron.statistics.index.shards'} | ${1.2} | ${400} | ${"[request body.cron.statistics.index.shards]: Number should be an integer."} ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${200} | ${null} ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${200} | ${null} ${'cron.statistics.interval'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.interval]: Interval is not valid."} @@ -353,6 +355,7 @@ hosts: ${'timeout'} | ${15000} | ${200} | ${null} ${'timeout'} | ${1000} | ${400} | ${'[request body.timeout]: Value should be greater or equal than 1500.'} ${'timeout'} | ${''} | ${400} | ${'[request body.timeout]: expected value of type [number] but got [string]'} + ${'timeout'} | ${1.2} | ${400} | ${"[request body.timeout]: Number should be an integer."} ${'wazuh.monitoring.creation'} | ${'h'} | ${200} | ${null} ${'wazuh.monitoring.creation'} | ${'d'} | ${200} | ${null} ${'wazuh.monitoring.creation'} | ${'w'} | ${200} | ${null} @@ -362,6 +365,7 @@ hosts: ${'wazuh.monitoring.enabled'} | ${0} | ${400} | ${'[request body.wazuh.monitoring.enabled]: expected value of type [boolean] but got [number]'} ${'wazuh.monitoring.frequency'} | ${100} | ${200} | ${null} ${'wazuh.monitoring.frequency'} | ${40} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Value should be greater or equal than 60."} + ${'wazuh.monitoring.frequency'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Number should be an integer."} ${'wazuh.monitoring.frequency'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.frequency]: expected value of type [number] but got [string]'} ${'wazuh.monitoring.pattern'} | ${'test'} | ${200} | ${null} ${'wazuh.monitoring.pattern'} | ${'test*'} | ${200} | ${null} @@ -382,10 +386,12 @@ hosts: ${'wazuh.monitoring.pattern'} | ${'test#'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} ${'wazuh.monitoring.replicas'} | ${0} | ${200} | ${null} ${'wazuh.monitoring.replicas'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Value should be greater or equal than 0."} + ${'wazuh.monitoring.replicas'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Number should be an integer."} ${'wazuh.monitoring.replicas'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.replicas]: expected value of type [number] but got [string]"} - ${'wazuh.monitoring.shards'} | ${1} | ${200} | ${null} - ${'wazuh.monitoring.shards'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.shards]: Value should be greater or equal than 1."} - ${'wazuh.monitoring.shards'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.shards]: expected value of type [number] but got [string]"} + ${'wazuh.monitoring.shards'} | ${1} | ${200} | ${null} + ${'wazuh.monitoring.shards'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.shards]: Value should be greater or equal than 1."} + ${'wazuh.monitoring.shards'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.shards]: Number should be an integer."} + ${'wazuh.monitoring.shards'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.shards]: expected value of type [number] but got [string]"} `(`$setting: $value - PUT /utils/configuration - $responseStatusCode`, async ({responseBodyMessage, responseStatusCode, setting, value}) => { const body = {[setting]: value}; const response = await supertest(innerServer.listener) From 92b9aacf42ce418fdd435ec84d83779896575245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Fri, 28 Oct 2022 13:40:54 +0200 Subject: [PATCH 74/79] test: fix tests --- common/plugin-settings.test.ts | 4 ++-- common/services/settings-validator.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/plugin-settings.test.ts b/common/plugin-settings.test.ts index 92a2a01255..ad26ad9be2 100644 --- a/common/plugin-settings.test.ts +++ b/common/plugin-settings.test.ts @@ -119,10 +119,10 @@ describe('[settings] Input validation', () => { ${'customization.logo.sidebar'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} ${'customization.reports.footer'} | ${'Test'} | ${undefined} ${'customization.reports.footer'} | ${'Test\nTest'} | ${undefined} - ${'customization.reports.footer'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or 2 line/s.'} + ${'customization.reports.footer'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or equal to 2 line/s.'} ${'customization.reports.header'} | ${'Test'} | ${undefined} ${'customization.reports.header'} | ${'Test\nTest'} | ${undefined} - ${'customization.reports.header'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or 4 line/s.'} + ${'customization.reports.header'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or equal to 4 line/s.'} ${'disabled_roles'} | ${['test']} | ${undefined} ${'disabled_roles'} | ${['']} | ${'Value can not be empty.'} ${'disabled_roles'} | ${['test space']} | ${"No whitespaces allowed."} diff --git a/common/services/settings-validator.ts b/common/services/settings-validator.ts index 8b11ea7ef8..66d604c1c9 100644 --- a/common/services/settings-validator.ts +++ b/common/services/settings-validator.ts @@ -63,7 +63,7 @@ export class SettingsValidator { return `The string should have more or ${options.min} line/s.`; }; if (typeof options.max !== 'undefined' && lines > options.max) { - return `The string should have less or ${options.max} line/s.`; + return `The string should have less or equal to ${options.max} line/s.`; }; } }; From 3973f0895a58ebb843acac8e61ad8e21ecbfd575 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Fri, 28 Oct 2022 17:41:50 +0200 Subject: [PATCH 75/79] Set textArea size --- common/constants.ts | 50 ++++++++++++------- .../common/form/input_text_area.tsx | 3 +- server/lib/reporting/printer.ts | 4 +- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index ee5544453b..1861b07571 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -358,6 +358,10 @@ export enum SettingCategory { CUSTOMIZATION, }; +type TPluginSettingOptionsTextArea = { + rowsSize: number +}; + type TPluginSettingOptionsSelect = { select: { text: string, value: any }[] }; @@ -442,7 +446,13 @@ export type TPluginSetting = { // Modify the setting requires restarting the plugin platform to take effect. requiresRestartingPluginPlatform?: boolean // Define options related to the `type`. - options?: TPluginSettingOptionsEditor | TPluginSettingOptionsFile | TPluginSettingOptionsNumber | TPluginSettingOptionsSelect | TPluginSettingOptionsSwitch + options?: + TPluginSettingOptionsEditor | + TPluginSettingOptionsFile | + TPluginSettingOptionsNumber | + TPluginSettingOptionsSelect | + TPluginSettingOptionsSwitch | + TPluginSettingOptionsTextArea // Transform the input value. The result is saved in the form global state of Settings/Configuration uiFormTransformChangedInputValue?: (value: any) => any // Transform the configuration value or default as initial value for the input in Settings/Configuration @@ -1080,23 +1090,29 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { defaultValueIfNotSet: REPORTS_PAGE_FOOTER_TEXT, isConfigurableFromFile: true, isConfigurableFromUI: true, - validate: SettingsValidator.multipleLinesString({max: 2}), - validateBackend: function(schema){ - return schema.string({validate: this.validate}); - }, - }, - "customization.reports.header": { - title: "Reports header", - description: "Set the header of the reports.", - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.textarea, - defaultValue: "", - defaultValueIfNotSet: REPORTS_PAGE_HEADER_TEXT, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - validate: SettingsValidator.multipleLinesString({max: 4}), + options: { rowsSize: 2 }, + validate: function (value) { + return SettingsValidator.multipleLinesString({ max: this.options.rowsSize })(value) + }, + validateBackend: function (schema) { + return schema.string({ validate: this.validate.bind(this) }); + }, + }, + "customization.reports.header": { + title: "Reports header", + description: "Set the header of the reports.", + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.textarea, + defaultValue: "", + defaultValueIfNotSet: REPORTS_PAGE_HEADER_TEXT, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { rowsSize: 3 }, + validate: function (value) { + return SettingsValidator.multipleLinesString({ max: this.options.rowsSize })(value) + }, validateBackend: function(schema){ - return schema.string({validate: this.validate}); + return schema.string({validate: this.validate?.bind(this)}); }, }, "disabled_roles": { diff --git a/public/components/common/form/input_text_area.tsx b/public/components/common/form/input_text_area.tsx index 67c45127d5..fc3561f2cc 100644 --- a/public/components/common/form/input_text_area.tsx +++ b/public/components/common/form/input_text_area.tsx @@ -2,13 +2,14 @@ import React from 'react'; import { EuiTextArea } from '@elastic/eui'; import { IInputFormType } from './types'; -export const InputFormTextArea = ({ value, isInvalid, onChange } : IInputFormType) => { +export const InputFormTextArea = ({ value, isInvalid, onChange, options } : IInputFormType) => { return ( ); }; diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index b27db3947c..ff1f1d247a 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -17,7 +17,7 @@ const COLORS = { PRIMARY: REPORTS_PRIMARY_COLOR }; -const pageConfiguration = ({pathToLogo, pageHeader, pageFooter}) => ({ +const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({ styles: { h1: { fontSize: 22, @@ -56,7 +56,7 @@ const pageConfiguration = ({pathToLogo, pageHeader, pageFooter}) => ({ columns: [ { image: path.join(__dirname, `../../../public/assets/${pathToLogo}`), - fit: [190,50] + fit: [190, 50] }, { text: pageHeader, From ad1b260eac6b778ebc3e1cece88e3497f7b57259 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Fri, 28 Oct 2022 19:01:22 +0200 Subject: [PATCH 76/79] Add max character length validation --- common/constants.ts | 17 ++++++++++++----- common/services/settings-validator.ts | 5 ++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 1861b07571..caf70e6427 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -359,7 +359,8 @@ export enum SettingCategory { }; type TPluginSettingOptionsTextArea = { - rowsSize: number + rowsSize?: number + maxLength?: number }; type TPluginSettingOptionsSelect = { @@ -1090,9 +1091,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { defaultValueIfNotSet: REPORTS_PAGE_FOOTER_TEXT, isConfigurableFromFile: true, isConfigurableFromUI: true, - options: { rowsSize: 2 }, + options: { rowsSize: 2, maxLength: 30 }, validate: function (value) { - return SettingsValidator.multipleLinesString({ max: this.options.rowsSize })(value) + return SettingsValidator.multipleLinesString({ + max: this.options.rowsSize, + maxLength: this.options.maxLength + })(value) }, validateBackend: function (schema) { return schema.string({ validate: this.validate.bind(this) }); @@ -1107,9 +1111,12 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { defaultValueIfNotSet: REPORTS_PAGE_HEADER_TEXT, isConfigurableFromFile: true, isConfigurableFromUI: true, - options: { rowsSize: 3 }, + options: { rowsSize: 3, maxLength: 20 }, validate: function (value) { - return SettingsValidator.multipleLinesString({ max: this.options.rowsSize })(value) + return SettingsValidator.multipleLinesString({ + max: this?.options?.rowsSize, + maxLength: this?.options?.maxLength + })(value) }, validateBackend: function(schema){ return schema.string({validate: this.validate?.bind(this)}); diff --git a/common/services/settings-validator.ts b/common/services/settings-validator.ts index 66d604c1c9..75fef368be 100644 --- a/common/services/settings-validator.ts +++ b/common/services/settings-validator.ts @@ -56,9 +56,12 @@ export class SettingsValidator { * @param options * @returns */ - static multipleLinesString(options: { min?: number, max?: number } = {}) { + static multipleLinesString(options: { min?: number, max?: number, maxLength?: number } = {}) { return function (value: number) { const lines = value.split(/\r\n|\r|\n/).length; + if (typeof options.maxLength !== 'undefined' && value.split('\n').some(line => line.length > options.maxLength)) { + return `The maximum length of a line is ${options.maxLength} characters.`; + }; if (typeof options.min !== 'undefined' && lines < options.min) { return `The string should have more or ${options.min} line/s.`; }; From 8cd8b8cd7fbbd5b11a2136667bd03c7e78eae3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 31 Oct 2022 09:00:41 +0100 Subject: [PATCH 77/79] test(settings): fix tests --- common/constants.ts | 4 +- common/plugin-settings.test.ts | 474 +++++++++--------- server/routes/wazuh-utils/wazuh-utils.test.ts | 440 ++++++++-------- 3 files changed, 463 insertions(+), 455 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index caf70e6427..dec85a3c86 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -1114,8 +1114,8 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { options: { rowsSize: 3, maxLength: 20 }, validate: function (value) { return SettingsValidator.multipleLinesString({ - max: this?.options?.rowsSize, - maxLength: this?.options?.maxLength + max: this.options.rowsSize, + maxLength: this.options?.maxLength })(value) }, validateBackend: function(schema){ diff --git a/common/plugin-settings.test.ts b/common/plugin-settings.test.ts index ad26ad9be2..d68f6dc4d2 100644 --- a/common/plugin-settings.test.ts +++ b/common/plugin-settings.test.ts @@ -2,241 +2,245 @@ import { PLUGIN_SETTINGS } from "./constants"; describe('[settings] Input validation', () => { it.each` - setting | value | expectedValidation - ${'alerts.sample.prefix'} | ${'test'} | ${undefined} - ${'alerts.sample.prefix'} | ${''} | ${"Value can not be empty."} - ${'alerts.sample.prefix'} | ${'test space'} | ${"No whitespaces allowed."} - ${'alerts.sample.prefix'} | ${'-test'} | ${"It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'_test'} | ${"It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'+test'} | ${"It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'.test'} | ${"It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'checks.api'} | ${true} | ${undefined} - ${'checks.api'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.fields'} | ${true} | ${undefined} - ${'checks.fields'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.maxBuckets'} | ${true} | ${undefined} - ${'checks.maxBuckets'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.pattern'} | ${true} | ${undefined} - ${'checks.pattern'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.setup'} | ${true} | ${undefined} - ${'checks.setup'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.template'} | ${true} | ${undefined} - ${'checks.template'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.timeFilter'} | ${true} | ${undefined} - ${'checks.timeFilter'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'cron.prefix'} | ${'test'} | ${undefined} - ${'cron.prefix'} | ${'test space'} | ${"No whitespaces allowed."} - ${'cron.prefix'} | ${''} | ${"Value can not be empty."} - ${'cron.prefix'} | ${'-test'} | ${"It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'_test'} | ${"It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'+test'} | ${"It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'.test'} | ${"It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.apis'} | ${['test']} | ${undefined} - ${'cron.statistics.apis'} | ${['test ']} | ${"No whitespaces allowed."} - ${'cron.statistics.apis'} | ${['']} | ${"Value can not be empty."} - ${'cron.statistics.apis'} | ${['test', 4]} | ${"Value is not a string."} - ${'cron.statistics.apis'} | ${'test space'} | ${"Value is not a valid list."} - ${'cron.statistics.apis'} | ${true} | ${"Value is not a valid list."} - ${'cron.statistics.index.creation'} | ${'h'} | ${undefined} - ${'cron.statistics.index.creation'} | ${'d'} | ${undefined} - ${'cron.statistics.index.creation'} | ${'w'} | ${undefined} - ${'cron.statistics.index.creation'} | ${'m'} | ${undefined} - ${'cron.statistics.index.creation'} | ${'test'} | ${"Invalid value. Allowed values: h, d, w, m."} - ${'cron.statistics.index.name'} | ${'test'} | ${undefined} - ${'cron.statistics.index.name'} | ${''} | ${"Value can not be empty."} - ${'cron.statistics.index.name'} | ${'test space'} | ${"No whitespaces allowed."} - ${'cron.statistics.index.name'} | ${'-test'} | ${"It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'_test'} | ${"It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'+test'} | ${"It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'.test'} | ${"It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.replicas'} | ${0} | ${undefined} - ${'cron.statistics.index.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} - ${'cron.statistics.index.replicas'} | ${'1.2'} | ${'Number should be an integer.'} - ${'cron.statistics.index.replicas'} | ${1.2} | ${'Number should be an integer.'} - ${'cron.statistics.index.shards'} | ${1} | ${undefined} - ${'cron.statistics.index.shards'} | ${-1} | ${"Value should be greater or equal than 1."} - ${'cron.statistics.index.shards'} | ${'1.2'} | ${'Number should be an integer.'} - ${'cron.statistics.index.shards'} | ${1.2} | ${'Number should be an integer.'} - ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${undefined} - ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${undefined} - ${'cron.statistics.interval'} | ${'custom'} | ${"Interval is not valid."} - ${'cron.statistics.interval'} | ${true} | ${"Interval is not valid."} - ${'cron.statistics.status'} | ${true} | ${undefined} - ${'cron.statistics.status'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'customization.logo.app'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} - ${'customization.logo.app'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} - ${'customization.logo.app'} | ${{size: 124000, name: 'image.png'}} | ${undefined} - ${'customization.logo.app'} | ${{size: 124000, name: 'image.svg'}} | ${undefined} - ${'customization.logo.app'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png, .svg.'} - ${'customization.logo.app'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} - ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} - ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} - ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.png'}} | ${undefined} - ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.svg'}} | ${undefined} - ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png, .svg.'} - ${'customization.logo.healthcheck'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} - ${'customization.logo.reports'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} - ${'customization.logo.reports'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} - ${'customization.logo.reports'} | ${{size: 124000, name: 'image.png'}} | ${undefined} - ${'customization.logo.reports'} | ${{size: 124000, name: 'image.svg'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png.'} - ${'customization.logo.reports'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png.'} - ${'customization.logo.reports'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} - ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} - ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} - ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.png'}} | ${undefined} - ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.svg'}} | ${undefined} - ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png, .svg.'} - ${'customization.logo.sidebar'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} - ${'customization.reports.footer'} | ${'Test'} | ${undefined} - ${'customization.reports.footer'} | ${'Test\nTest'} | ${undefined} - ${'customization.reports.footer'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or equal to 2 line/s.'} - ${'customization.reports.header'} | ${'Test'} | ${undefined} - ${'customization.reports.header'} | ${'Test\nTest'} | ${undefined} - ${'customization.reports.header'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or equal to 4 line/s.'} - ${'disabled_roles'} | ${['test']} | ${undefined} - ${'disabled_roles'} | ${['']} | ${'Value can not be empty.'} - ${'disabled_roles'} | ${['test space']} | ${"No whitespaces allowed."} - ${'disabled_roles'} | ${['test', 4]} | ${"Value is not a string."} - ${'enrollment.dns'} | ${'test'} | ${undefined} - ${'enrollment.dns'} | ${''} | ${undefined} - ${'enrollment.dns'} | ${'test space'} | ${"No whitespaces allowed."} - ${'enrollment.password'} | ${'test'} | ${undefined} - ${'enrollment.password'} | ${''} | ${"Value can not be empty."} - ${'enrollment.password'} | ${'test space'} | ${undefined} - ${'extensions.audit'} | ${true} | ${undefined} - ${'extensions.audit'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.aws'} | ${true} | ${undefined} - ${'extensions.aws'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.ciscat'} | ${true} | ${undefined} - ${'extensions.ciscat'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.gcp'} | ${true} | ${undefined} - ${'extensions.gcp'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.gdpr'} | ${true} | ${undefined} - ${'extensions.gdpr'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.hipaa'} | ${true} | ${undefined} - ${'extensions.hipaa'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.nist'} | ${true} | ${undefined} - ${'extensions.nist'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.oscap'} | ${true} | ${undefined} - ${'extensions.oscap'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.osquery'} | ${true} | ${undefined} - ${'extensions.osquery'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.pci'} | ${true} | ${undefined} - ${'extensions.pci'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.tsc'} | ${true} | ${undefined} - ${'extensions.tsc'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'extensions.virustotal'} | ${true} | ${undefined} - ${'extensions.virustotal'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'ip.ignore'} | ${['test']} | ${undefined} - ${'ip.ignore'} | ${['test*']} | ${undefined} - ${'ip.ignore'} | ${['']} | ${'Value can not be empty.'} - ${'ip.ignore'} | ${['test space']} | ${"No whitespaces allowed."} - ${'ip.ignore'} | ${true} | ${"Value is not a valid list."} - ${'ip.ignore'} | ${['-test']} | ${"It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['_test']} | ${"It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['+test']} | ${"It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['.test']} | ${"It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['test\\']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test/']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test?']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test"']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test<']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test>']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test|']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test,']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test#']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test', 'test#']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.selector'} | ${true} | ${undefined} - ${'ip.selector'} | ${''} | ${'It should be a boolean. Allowed values: true or false.'} - ${'logs.level'} | ${'info'} | ${undefined} - ${'logs.level'} | ${'debug'} | ${undefined} - ${'logs.level'} | ${''} | ${'Invalid value. Allowed values: info, debug.'} - ${'pattern'} | ${'test'} | ${undefined} - ${'pattern'} | ${'test*'} | ${undefined} - ${'pattern'} | ${''} | ${'Value can not be empty.'} - ${'pattern'} | ${'test space'} | ${"No whitespaces allowed."} - ${'pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} - ${'pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} - ${'pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} - ${'pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} - ${'pattern'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'timeout'} | ${15000} | ${undefined} - ${'timeout'} | ${1000} | ${'Value should be greater or equal than 1500.'} - ${'timeout'} | ${''} | ${'Value should be greater or equal than 1500.'} - ${'timeout'} | ${'1.2'} | ${'Number should be an integer.'} - ${'timeout'} | ${1.2} | ${'Number should be an integer.'} - ${'wazuh.monitoring.creation'} | ${'h'} | ${undefined} - ${'wazuh.monitoring.creation'} | ${'d'} | ${undefined} - ${'wazuh.monitoring.creation'} | ${'w'} | ${undefined} - ${'wazuh.monitoring.creation'} | ${'m'} | ${undefined} - ${'wazuh.monitoring.creation'} | ${'test'} | ${"Invalid value. Allowed values: h, d, w, m."} - ${'wazuh.monitoring.enabled'} | ${true} | ${undefined} - ${'wazuh.monitoring.frequency'} | ${100} | ${undefined} - ${'wazuh.monitoring.frequency'} | ${40} | ${"Value should be greater or equal than 60."} - ${'wazuh.monitoring.frequency'} | ${'1.2'} | ${'Number should be an integer.'} - ${'wazuh.monitoring.frequency'} | ${1.2} | ${'Number should be an integer.'} - ${'wazuh.monitoring.pattern'} | ${'test'} | ${undefined} - ${'wazuh.monitoring.pattern'} | ${'test*'} | ${undefined} - ${'wazuh.monitoring.pattern'} | ${''} | ${'Value can not be empty.'} - ${'wazuh.monitoring.pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.replicas'} | ${0} | ${undefined} - ${'wazuh.monitoring.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} - ${'wazuh.monitoring.replicas'} | ${'1.2'} | ${'Number should be an integer.'} - ${'wazuh.monitoring.replicas'} | ${1.2} | ${'Number should be an integer.'} - ${'wazuh.monitoring.shards'} | ${1} | ${undefined} - ${'wazuh.monitoring.shards'} | ${-1} | ${"Value should be greater or equal than 1."} - ${'wazuh.monitoring.shards'} | ${'1.2'} | ${'Number should be an integer.'} - ${'wazuh.monitoring.shards'} | ${1.2} | ${'Number should be an integer.'} + setting | value | expectedValidation + ${'alerts.sample.prefix'} | ${'test'} | ${undefined} + ${'alerts.sample.prefix'} | ${''} | ${"Value can not be empty."} + ${'alerts.sample.prefix'} | ${'test space'} | ${"No whitespaces allowed."} + ${'alerts.sample.prefix'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'checks.api'} | ${true} | ${undefined} + ${'checks.api'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.fields'} | ${true} | ${undefined} + ${'checks.fields'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.maxBuckets'} | ${true} | ${undefined} + ${'checks.maxBuckets'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.pattern'} | ${true} | ${undefined} + ${'checks.pattern'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.setup'} | ${true} | ${undefined} + ${'checks.setup'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.template'} | ${true} | ${undefined} + ${'checks.template'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'checks.timeFilter'} | ${true} | ${undefined} + ${'checks.timeFilter'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'cron.prefix'} | ${'test'} | ${undefined} + ${'cron.prefix'} | ${'test space'} | ${"No whitespaces allowed."} + ${'cron.prefix'} | ${''} | ${"Value can not be empty."} + ${'cron.prefix'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.apis'} | ${['test']} | ${undefined} + ${'cron.statistics.apis'} | ${['test ']} | ${"No whitespaces allowed."} + ${'cron.statistics.apis'} | ${['']} | ${"Value can not be empty."} + ${'cron.statistics.apis'} | ${['test', 4]} | ${"Value is not a string."} + ${'cron.statistics.apis'} | ${'test space'} | ${"Value is not a valid list."} + ${'cron.statistics.apis'} | ${true} | ${"Value is not a valid list."} + ${'cron.statistics.index.creation'} | ${'h'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'d'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'w'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'m'} | ${undefined} + ${'cron.statistics.index.creation'} | ${'test'} | ${"Invalid value. Allowed values: h, d, w, m."} + ${'cron.statistics.index.name'} | ${'test'} | ${undefined} + ${'cron.statistics.index.name'} | ${''} | ${"Value can not be empty."} + ${'cron.statistics.index.name'} | ${'test space'} | ${"No whitespaces allowed."} + ${'cron.statistics.index.name'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test*'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.replicas'} | ${0} | ${undefined} + ${'cron.statistics.index.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} + ${'cron.statistics.index.replicas'} | ${'1.2'} | ${'Number should be an integer.'} + ${'cron.statistics.index.replicas'} | ${1.2} | ${'Number should be an integer.'} + ${'cron.statistics.index.shards'} | ${1} | ${undefined} + ${'cron.statistics.index.shards'} | ${-1} | ${"Value should be greater or equal than 1."} + ${'cron.statistics.index.shards'} | ${'1.2'} | ${'Number should be an integer.'} + ${'cron.statistics.index.shards'} | ${1.2} | ${'Number should be an integer.'} + ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${undefined} + ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${undefined} + ${'cron.statistics.interval'} | ${'custom'} | ${"Interval is not valid."} + ${'cron.statistics.interval'} | ${true} | ${"Interval is not valid."} + ${'cron.statistics.status'} | ${true} | ${undefined} + ${'cron.statistics.status'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'customization.logo.app'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} + ${'customization.logo.app'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} + ${'customization.logo.app'} | ${{size: 124000, name: 'image.png'}} | ${undefined} + ${'customization.logo.app'} | ${{size: 124000, name: 'image.svg'}} | ${undefined} + ${'customization.logo.app'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png, .svg.'} + ${'customization.logo.app'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} + ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} + ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} + ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.png'}} | ${undefined} + ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.svg'}} | ${undefined} + ${'customization.logo.healthcheck'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png, .svg.'} + ${'customization.logo.healthcheck'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} + ${'customization.logo.reports'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} + ${'customization.logo.reports'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} + ${'customization.logo.reports'} | ${{size: 124000, name: 'image.png'}} | ${undefined} + ${'customization.logo.reports'} | ${{size: 124000, name: 'image.svg'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png.'} + ${'customization.logo.reports'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png.'} + ${'customization.logo.reports'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} + ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.jpg'}} | ${undefined} + ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.jpeg'}} | ${undefined} + ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.png'}} | ${undefined} + ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.svg'}} | ${undefined} + ${'customization.logo.sidebar'} | ${{size: 124000, name: 'image.txt'}} | ${'File extension is invalid. Allowed file extensions: .jpeg, .jpg, .png, .svg.'} + ${'customization.logo.sidebar'} | ${{size: 1240000, name: 'image.txt'}} | ${'File size should be lower or equal than 1 MB.'} + ${'customization.reports.footer'} | ${'Test'} | ${undefined} + ${'customization.reports.footer'} | ${'Test\nTest'} | ${undefined} + ${'customization.reports.footer'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or equal to 2 line/s.'} + ${'customization.reports.footer'} | ${'Line with 30 characters \nTest'} | ${undefined} + ${'customization.reports.footer'} | ${'Line with 31 characters \nTest'} | ${"The maximum length of a line is 30 characters."} + ${'customization.reports.header'} | ${'Test'} | ${undefined} + ${'customization.reports.header'} | ${'Test\nTest'} | ${undefined} + ${'customization.reports.header'} | ${'Test\nTest\nTest\nTest\nTest'} | ${'The string should have less or equal to 3 line/s.'} + ${'customization.reports.header'} | ${'Line with 20 charact\nTest'} | ${undefined} + ${'customization.reports.header'} | ${'Line with 23 characters\nTest'} | ${"The maximum length of a line is 20 characters."} + ${'disabled_roles'} | ${['test']} | ${undefined} + ${'disabled_roles'} | ${['']} | ${'Value can not be empty.'} + ${'disabled_roles'} | ${['test space']} | ${"No whitespaces allowed."} + ${'disabled_roles'} | ${['test', 4]} | ${"Value is not a string."} + ${'enrollment.dns'} | ${'test'} | ${undefined} + ${'enrollment.dns'} | ${''} | ${undefined} + ${'enrollment.dns'} | ${'test space'} | ${"No whitespaces allowed."} + ${'enrollment.password'} | ${'test'} | ${undefined} + ${'enrollment.password'} | ${''} | ${"Value can not be empty."} + ${'enrollment.password'} | ${'test space'} | ${undefined} + ${'extensions.audit'} | ${true} | ${undefined} + ${'extensions.audit'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.aws'} | ${true} | ${undefined} + ${'extensions.aws'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.ciscat'} | ${true} | ${undefined} + ${'extensions.ciscat'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.gcp'} | ${true} | ${undefined} + ${'extensions.gcp'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.gdpr'} | ${true} | ${undefined} + ${'extensions.gdpr'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.hipaa'} | ${true} | ${undefined} + ${'extensions.hipaa'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.nist'} | ${true} | ${undefined} + ${'extensions.nist'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.oscap'} | ${true} | ${undefined} + ${'extensions.oscap'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.osquery'} | ${true} | ${undefined} + ${'extensions.osquery'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.pci'} | ${true} | ${undefined} + ${'extensions.pci'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.tsc'} | ${true} | ${undefined} + ${'extensions.tsc'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'extensions.virustotal'} | ${true} | ${undefined} + ${'extensions.virustotal'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} + ${'ip.ignore'} | ${['test']} | ${undefined} + ${'ip.ignore'} | ${['test*']} | ${undefined} + ${'ip.ignore'} | ${['']} | ${'Value can not be empty.'} + ${'ip.ignore'} | ${['test space']} | ${"No whitespaces allowed."} + ${'ip.ignore'} | ${true} | ${"Value is not a valid list."} + ${'ip.ignore'} | ${['-test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['_test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['+test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['.test']} | ${"It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['test\\']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test/']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test?']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test"']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test<']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test>']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test|']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test,']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test#']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test', 'test#']} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.selector'} | ${true} | ${undefined} + ${'ip.selector'} | ${''} | ${'It should be a boolean. Allowed values: true or false.'} + ${'logs.level'} | ${'info'} | ${undefined} + ${'logs.level'} | ${'debug'} | ${undefined} + ${'logs.level'} | ${''} | ${'Invalid value. Allowed values: info, debug.'} + ${'pattern'} | ${'test'} | ${undefined} + ${'pattern'} | ${'test*'} | ${undefined} + ${'pattern'} | ${''} | ${'Value can not be empty.'} + ${'pattern'} | ${'test space'} | ${"No whitespaces allowed."} + ${'pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'pattern'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'timeout'} | ${15000} | ${undefined} + ${'timeout'} | ${1000} | ${'Value should be greater or equal than 1500.'} + ${'timeout'} | ${''} | ${'Value should be greater or equal than 1500.'} + ${'timeout'} | ${'1.2'} | ${'Number should be an integer.'} + ${'timeout'} | ${1.2} | ${'Number should be an integer.'} + ${'wazuh.monitoring.creation'} | ${'h'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'d'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'w'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'m'} | ${undefined} + ${'wazuh.monitoring.creation'} | ${'test'} | ${"Invalid value. Allowed values: h, d, w, m."} + ${'wazuh.monitoring.enabled'} | ${true} | ${undefined} + ${'wazuh.monitoring.frequency'} | ${100} | ${undefined} + ${'wazuh.monitoring.frequency'} | ${40} | ${"Value should be greater or equal than 60."} + ${'wazuh.monitoring.frequency'} | ${'1.2'} | ${'Number should be an integer.'} + ${'wazuh.monitoring.frequency'} | ${1.2} | ${'Number should be an integer.'} + ${'wazuh.monitoring.pattern'} | ${'test'} | ${undefined} + ${'wazuh.monitoring.pattern'} | ${'test*'} | ${undefined} + ${'wazuh.monitoring.pattern'} | ${''} | ${'Value can not be empty.'} + ${'wazuh.monitoring.pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'test\\'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test/'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test?'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test"'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test<'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test>'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test|'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test,'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test#'} | ${"It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.replicas'} | ${0} | ${undefined} + ${'wazuh.monitoring.replicas'} | ${-1} | ${"Value should be greater or equal than 0."} + ${'wazuh.monitoring.replicas'} | ${'1.2'} | ${'Number should be an integer.'} + ${'wazuh.monitoring.replicas'} | ${1.2} | ${'Number should be an integer.'} + ${'wazuh.monitoring.shards'} | ${1} | ${undefined} + ${'wazuh.monitoring.shards'} | ${-1} | ${"Value should be greater or equal than 1."} + ${'wazuh.monitoring.shards'} | ${'1.2'} | ${'Number should be an integer.'} + ${'wazuh.monitoring.shards'} | ${1.2} | ${'Number should be an integer.'} `('$setting | $value | $expectedValidation', ({ setting, value, expectedValidation }) => { expect( PLUGIN_SETTINGS[setting].validate( diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts index 530953e2f9..9cf0562dc6 100644 --- a/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -206,224 +206,228 @@ hosts: it.each` setting | value | responseStatusCode | responseBodyMessage - ${'alerts.sample.prefix'} | ${'test'} | ${200} | ${null} - ${'alerts.sample.prefix'} | ${''} | ${400} | ${"[request body.alerts.sample.prefix]: Value can not be empty."} - ${'alerts.sample.prefix'} | ${'test space'} | ${400} | ${"[request body.alerts.sample.prefix]: No whitespaces allowed."} - ${'alerts.sample.prefix'} | ${4} | ${400} | ${'[request body.alerts.sample.prefix]: expected value of type [string] but got [number]'} - ${'alerts.sample.prefix'} | ${'-test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'_test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'+test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'.test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} - ${'alerts.sample.prefix'} | ${'test\\'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test/'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test?'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test"'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test<'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test>'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test|'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test,'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test#'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'alerts.sample.prefix'} | ${'test*'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'checks.api'} | ${true} | ${200} | ${null} - ${'checks.api'} | ${0} | ${400} | ${'[request body.checks.api]: expected value of type [boolean] but got [number]'} - ${'checks.fields'} | ${true} | ${200} | ${null} - ${'checks.fields'} | ${0} | ${400} | ${'[request body.checks.fields]: expected value of type [boolean] but got [number]'} - ${'checks.maxBuckets'} | ${true} | ${200} | ${null} - ${'checks.maxBuckets'} | ${0} | ${400} | ${'[request body.checks.maxBuckets]: expected value of type [boolean] but got [number]'} - ${'checks.pattern'} | ${true} | ${200} | ${null} - ${'checks.pattern'} | ${0} | ${400} | ${'[request body.checks.pattern]: expected value of type [boolean] but got [number]'} - ${'checks.setup'} | ${true} | ${200} | ${null} - ${'checks.setup'} | ${0} | ${400} | ${'[request body.checks.setup]: expected value of type [boolean] but got [number]'} - ${'checks.template'} | ${true} | ${200} | ${null} - ${'checks.template'} | ${0} | ${400} | ${'[request body.checks.template]: expected value of type [boolean] but got [number]'} - ${'checks.timeFilter'} | ${true} | ${200} | ${null} - ${'checks.timeFilter'} | ${0} | ${400} | ${'[request body.checks.timeFilter]: expected value of type [boolean] but got [number]'} - ${'cron.prefix'} | ${'test'} | ${200} | ${null} - ${'cron.prefix'} | ${'test space'} | ${400} | ${"[request body.cron.prefix]: No whitespaces allowed."} - ${'cron.prefix'} | ${''} | ${400} | ${"[request body.cron.prefix]: Value can not be empty."} - ${'cron.prefix'} | ${4} | ${400} | ${'[request body.cron.prefix]: expected value of type [string] but got [number]'} - ${'cron.prefix'} | ${'-test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'_test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'+test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'.test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} - ${'cron.prefix'} | ${'test\\'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test/'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test?'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test"'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test<'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test>'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test|'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test,'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test#'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.prefix'} | ${'test*'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.apis'} | ${['test']} | ${200} | ${null} - ${'cron.statistics.apis'} | ${['test ']} | ${400} | ${"[request body.cron.statistics.apis.0]: No whitespaces allowed."} - ${'cron.statistics.apis'} | ${['']} | ${400} | ${"[request body.cron.statistics.apis.0]: Value can not be empty."} - ${'cron.statistics.apis'} | ${['test', 4]} | ${400} | ${"[request body.cron.statistics.apis.1]: expected value of type [string] but got [number]"} - ${'cron.statistics.apis'} | ${'test space'} | ${400} | ${"[request body.cron.statistics.apis]: could not parse array value from json input"} - ${'cron.statistics.apis'} | ${true} | ${400} | ${"[request body.cron.statistics.apis]: expected value of type [array] but got [boolean]"} - ${'cron.statistics.index.creation'} | ${'h'} | ${200} | ${null} - ${'cron.statistics.index.creation'} | ${'d'} | ${200} | ${null} - ${'cron.statistics.index.creation'} | ${'w'} | ${200} | ${null} - ${'cron.statistics.index.creation'} | ${'m'} | ${200} | ${null} - ${'cron.statistics.index.creation'} | ${'test'} | ${400} | ${"[request body.cron.statistics.index.creation]: types that failed validation:\n- [request body.cron.statistics.index.creation.0]: expected value to equal [h]\n- [request body.cron.statistics.index.creation.1]: expected value to equal [d]\n- [request body.cron.statistics.index.creation.2]: expected value to equal [w]\n- [request body.cron.statistics.index.creation.3]: expected value to equal [m]"} - ${'cron.statistics.index.name'} | ${'test'} | ${200} | ${null} - ${'cron.statistics.index.name'} | ${''} | ${400} | ${"[request body.cron.statistics.index.name]: Value can not be empty."} - ${'cron.statistics.index.name'} | ${'test space'} | ${400} | ${"[request body.cron.statistics.index.name]: No whitespaces allowed."} - ${'cron.statistics.index.name'} | ${true} | ${400} | ${"[request body.cron.statistics.index.name]: expected value of type [string] but got [boolean]"} - ${'cron.statistics.index.name'} | ${'-test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'_test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'+test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'.test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} - ${'cron.statistics.index.name'} | ${'test\\'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test/'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test?'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test"'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test<'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test>'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test|'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test,'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test#'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.name'} | ${'test*'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} - ${'cron.statistics.index.replicas'} | ${0} | ${200} | ${null} - ${'cron.statistics.index.replicas'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.replicas]: Value should be greater or equal than 0."} - ${'cron.statistics.index.replicas'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.index.replicas]: expected value of type [number] but got [string]"} - ${'cron.statistics.index.replicas'} | ${1.2} | ${400} | ${"[request body.cron.statistics.index.replicas]: Number should be an integer."} - ${'cron.statistics.index.shards'} | ${1} | ${200} | ${null} - ${'cron.statistics.index.shards'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.shards]: Value should be greater or equal than 1."} - ${'cron.statistics.index.shards'} | ${1.2} | ${400} | ${"[request body.cron.statistics.index.shards]: Number should be an integer."} - ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${200} | ${null} - ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${200} | ${null} - ${'cron.statistics.interval'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.interval]: Interval is not valid."} - ${'cron.statistics.interval'} | ${true} | ${400} | ${"[request body.cron.statistics.interval]: expected value of type [string] but got [boolean]"} - ${'cron.statistics.status'} | ${true} | ${200} | ${null} - ${'cron.statistics.status'} | ${0} | ${400} | ${'[request body.cron.statistics.status]: expected value of type [boolean] but got [number]'} - ${'customization.reports.footer'} | ${'Test'} | ${200} | ${null} - ${'customization.reports.footer'} | ${'Test\nTest'} | ${200} | ${null} - ${'customization.reports.footer'} | ${'Test\nTest\nTest\nTest\nTest'} | ${400} | ${"[request body.customization.reports.footer]: The string should have less or equal to 2 line/s."} - ${'customization.reports.footer'} | ${true} | ${400} | ${'[request body.customization.reports.footer]: expected value of type [string] but got [boolean]'} - ${'customization.reports.header'} | ${'Test'} | ${200} | ${null} - ${'customization.reports.header'} | ${'Test\nTest'} | ${200} | ${null} - ${'customization.reports.header'} | ${'Test\nTest\nTest\nTest\nTest'} | ${400} | ${"[request body.customization.reports.header]: The string should have less or equal to 4 line/s."} - ${'customization.reports.header'} | ${true} | ${400} | ${'[request body.customization.reports.header]: expected value of type [string] but got [boolean]'} - ${'disabled_roles'} | ${['test']} | ${200} | ${null} - ${'disabled_roles'} | ${['']} | ${400} | ${'[request body.disabled_roles.0]: Value can not be empty.'} - ${'disabled_roles'} | ${['test space']} | ${400} | ${"[request body.disabled_roles.0]: No whitespaces allowed."} - ${'disabled_roles'} | ${['test', 4]} | ${400} | ${"[request body.disabled_roles.1]: expected value of type [string] but got [number]"} - ${'enrollment.dns'} | ${'test'} | ${200} | ${null} - ${'enrollment.dns'} | ${''} | ${200} | ${null} - ${'enrollment.dns'} | ${'test space'} | ${400} | ${"[request body.enrollment.dns]: No whitespaces allowed."} - ${'enrollment.dns'} | ${true} | ${400} | ${'[request body.enrollment.dns]: expected value of type [string] but got [boolean]'} - ${'enrollment.password'} | ${'test'} | ${200} | ${null} - ${'enrollment.password'} | ${''} | ${400} | ${"[request body.enrollment.password]: Value can not be empty."} - ${'enrollment.password'} | ${'test space'} | ${200} | ${null} - ${'enrollment.password'} | ${true} | ${400} | ${'[request body.enrollment.password]: expected value of type [string] but got [boolean]'} - ${'extensions.audit'} | ${true} | ${200} | ${null} - ${'extensions.audit'} | ${0} | ${400} | ${'[request body.extensions.audit]: expected value of type [boolean] but got [number]'} - ${'extensions.aws'} | ${true} | ${200} | ${null} - ${'extensions.aws'} | ${0} | ${400} | ${'[request body.extensions.aws]: expected value of type [boolean] but got [number]'} - ${'extensions.ciscat'} | ${true} | ${200} | ${null} - ${'extensions.ciscat'} | ${0} | ${400} | ${'[request body.extensions.ciscat]: expected value of type [boolean] but got [number]'} - ${'extensions.gcp'} | ${true} | ${200} | ${null} - ${'extensions.gcp'} | ${0} | ${400} | ${'[request body.extensions.gcp]: expected value of type [boolean] but got [number]'} - ${'extensions.gdpr'} | ${true} | ${200} | ${null} - ${'extensions.gdpr'} | ${0} | ${400} | ${'[request body.extensions.gdpr]: expected value of type [boolean] but got [number]'} - ${'extensions.hipaa'} | ${true} | ${200} | ${null} - ${'extensions.hipaa'} | ${0} | ${400} | ${'[request body.extensions.hipaa]: expected value of type [boolean] but got [number]'} - ${'extensions.nist'} | ${true} | ${200} | ${null} - ${'extensions.nist'} | ${0} | ${400} | ${'[request body.extensions.nist]: expected value of type [boolean] but got [number]'} - ${'extensions.oscap'} | ${true} | ${200} | ${null} - ${'extensions.oscap'} | ${0} | ${400} | ${'[request body.extensions.oscap]: expected value of type [boolean] but got [number]'} - ${'extensions.osquery'} | ${true} | ${200} | ${null} - ${'extensions.osquery'} | ${0} | ${400} | ${'[request body.extensions.osquery]: expected value of type [boolean] but got [number]'} - ${'extensions.pci'} | ${true} | ${200} | ${null} - ${'extensions.pci'} | ${0} | ${400} | ${'[request body.extensions.pci]: expected value of type [boolean] but got [number]'} - ${'extensions.tsc'} | ${true} | ${200} | ${null} - ${'extensions.tsc'} | ${0} | ${400} | ${'[request body.extensions.tsc]: expected value of type [boolean] but got [number]'} - ${'extensions.virustotal'} | ${true} | ${200} | ${null} - ${'extensions.virustotal'} | ${0} | ${400} | ${'[request body.extensions.virustotal]: expected value of type [boolean] but got [number]'} - ${'ip.ignore'} | ${['test']} | ${200} | ${null} - ${'ip.ignore'} | ${['test*']} | ${200} | ${null} - ${'ip.ignore'} | ${['']} | ${400} | ${'[request body.ip.ignore.0]: Value can not be empty.'} - ${'ip.ignore'} | ${['test space']} | ${400} | ${"[request body.ip.ignore.0]: No whitespaces allowed."} - ${'ip.ignore'} | ${true} | ${400} | ${"[request body.ip.ignore]: expected value of type [array] but got [boolean]"} - ${'ip.ignore'} | ${['-test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['_test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['+test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['.test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} - ${'ip.ignore'} | ${['test\\']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test/']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test?']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test"']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test<']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test>']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test|']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test,']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test#']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.ignore'} | ${['test', 'test#']} | ${400} | ${"[request body.ip.ignore.1]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'ip.selector'} | ${true} | ${200} | ${null} - ${'ip.selector'} | ${''} | ${400} | ${'[request body.ip.selector]: expected value of type [boolean] but got [string]'} - ${'logs.level'} | ${'info'} | ${200} | ${null} - ${'logs.level'} | ${'debug'} | ${200} | ${null} - ${'logs.level'} | ${''} | ${400} | ${'[request body.logs.level]: types that failed validation:\n- [request body.logs.level.0]: expected value to equal [info]\n- [request body.logs.level.1]: expected value to equal [debug]'} - ${'pattern'} | ${'test'} | ${200} | ${null} - ${'pattern'} | ${'test*'} | ${200} | ${null} - ${'pattern'} | ${''} | ${400} | ${'[request body.pattern]: Value can not be empty.'} - ${'pattern'} | ${'test space'} | ${400} | ${"[request body.pattern]: No whitespaces allowed."} - ${'pattern'} | ${true} | ${400} | ${'[request body.pattern]: expected value of type [string] but got [boolean]'} - ${'pattern'} | ${'-test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} - ${'pattern'} | ${'_test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} - ${'pattern'} | ${'+test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} - ${'pattern'} | ${'.test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} - ${'pattern'} | ${'test\\'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test/'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test?'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test"'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test<'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test>'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test|'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test,'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'pattern'} | ${'test#'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'timeout'} | ${15000} | ${200} | ${null} - ${'timeout'} | ${1000} | ${400} | ${'[request body.timeout]: Value should be greater or equal than 1500.'} - ${'timeout'} | ${''} | ${400} | ${'[request body.timeout]: expected value of type [number] but got [string]'} - ${'timeout'} | ${1.2} | ${400} | ${"[request body.timeout]: Number should be an integer."} - ${'wazuh.monitoring.creation'} | ${'h'} | ${200} | ${null} - ${'wazuh.monitoring.creation'} | ${'d'} | ${200} | ${null} - ${'wazuh.monitoring.creation'} | ${'w'} | ${200} | ${null} - ${'wazuh.monitoring.creation'} | ${'m'} | ${200} | ${null} - ${'wazuh.monitoring.creation'} | ${'test'} | ${400} | ${"[request body.wazuh.monitoring.creation]: types that failed validation:\n- [request body.wazuh.monitoring.creation.0]: expected value to equal [h]\n- [request body.wazuh.monitoring.creation.1]: expected value to equal [d]\n- [request body.wazuh.monitoring.creation.2]: expected value to equal [w]\n- [request body.wazuh.monitoring.creation.3]: expected value to equal [m]"} - ${'wazuh.monitoring.enabled'} | ${true} | ${200} | ${null} - ${'wazuh.monitoring.enabled'} | ${0} | ${400} | ${'[request body.wazuh.monitoring.enabled]: expected value of type [boolean] but got [number]'} - ${'wazuh.monitoring.frequency'} | ${100} | ${200} | ${null} - ${'wazuh.monitoring.frequency'} | ${40} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Value should be greater or equal than 60."} - ${'wazuh.monitoring.frequency'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Number should be an integer."} - ${'wazuh.monitoring.frequency'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.frequency]: expected value of type [number] but got [string]'} - ${'wazuh.monitoring.pattern'} | ${'test'} | ${200} | ${null} - ${'wazuh.monitoring.pattern'} | ${'test*'} | ${200} | ${null} - ${'wazuh.monitoring.pattern'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.pattern]: value has length [0] but it must have a minimum length of [1].'} - ${'wazuh.monitoring.pattern'} | ${true} | ${400} | ${'[request body.wazuh.monitoring.pattern]: expected value of type [string] but got [boolean]'} - ${'wazuh.monitoring.pattern'} | ${'-test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'_test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'+test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'.test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} - ${'wazuh.monitoring.pattern'} | ${'test\\'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test/'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test?'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test"'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test<'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test>'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test|'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test,'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.pattern'} | ${'test#'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} - ${'wazuh.monitoring.replicas'} | ${0} | ${200} | ${null} - ${'wazuh.monitoring.replicas'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Value should be greater or equal than 0."} - ${'wazuh.monitoring.replicas'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Number should be an integer."} - ${'wazuh.monitoring.replicas'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.replicas]: expected value of type [number] but got [string]"} - ${'wazuh.monitoring.shards'} | ${1} | ${200} | ${null} - ${'wazuh.monitoring.shards'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.shards]: Value should be greater or equal than 1."} - ${'wazuh.monitoring.shards'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.shards]: Number should be an integer."} - ${'wazuh.monitoring.shards'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.shards]: expected value of type [number] but got [string]"} + ${'alerts.sample.prefix'} | ${'test'} | ${200} | ${null} + ${'alerts.sample.prefix'} | ${''} | ${400} | ${"[request body.alerts.sample.prefix]: Value can not be empty."} + ${'alerts.sample.prefix'} | ${'test space'} | ${400} | ${"[request body.alerts.sample.prefix]: No whitespaces allowed."} + ${'alerts.sample.prefix'} | ${4} | ${400} | ${'[request body.alerts.sample.prefix]: expected value of type [string] but got [number]'} + ${'alerts.sample.prefix'} | ${'-test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'_test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'+test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'.test'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't start with: -, _, +, .."} + ${'alerts.sample.prefix'} | ${'test\\'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test/'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test?'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test"'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test<'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test>'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test|'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test,'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test#'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'alerts.sample.prefix'} | ${'test*'} | ${400} | ${"[request body.alerts.sample.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'checks.api'} | ${true} | ${200} | ${null} + ${'checks.api'} | ${0} | ${400} | ${'[request body.checks.api]: expected value of type [boolean] but got [number]'} + ${'checks.fields'} | ${true} | ${200} | ${null} + ${'checks.fields'} | ${0} | ${400} | ${'[request body.checks.fields]: expected value of type [boolean] but got [number]'} + ${'checks.maxBuckets'} | ${true} | ${200} | ${null} + ${'checks.maxBuckets'} | ${0} | ${400} | ${'[request body.checks.maxBuckets]: expected value of type [boolean] but got [number]'} + ${'checks.pattern'} | ${true} | ${200} | ${null} + ${'checks.pattern'} | ${0} | ${400} | ${'[request body.checks.pattern]: expected value of type [boolean] but got [number]'} + ${'checks.setup'} | ${true} | ${200} | ${null} + ${'checks.setup'} | ${0} | ${400} | ${'[request body.checks.setup]: expected value of type [boolean] but got [number]'} + ${'checks.template'} | ${true} | ${200} | ${null} + ${'checks.template'} | ${0} | ${400} | ${'[request body.checks.template]: expected value of type [boolean] but got [number]'} + ${'checks.timeFilter'} | ${true} | ${200} | ${null} + ${'checks.timeFilter'} | ${0} | ${400} | ${'[request body.checks.timeFilter]: expected value of type [boolean] but got [number]'} + ${'cron.prefix'} | ${'test'} | ${200} | ${null} + ${'cron.prefix'} | ${'test space'} | ${400} | ${"[request body.cron.prefix]: No whitespaces allowed."} + ${'cron.prefix'} | ${''} | ${400} | ${"[request body.cron.prefix]: Value can not be empty."} + ${'cron.prefix'} | ${4} | ${400} | ${'[request body.cron.prefix]: expected value of type [string] but got [number]'} + ${'cron.prefix'} | ${'-test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'_test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'+test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'.test'} | ${400} | ${"[request body.cron.prefix]: It can't start with: -, _, +, .."} + ${'cron.prefix'} | ${'test\\'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test/'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test?'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test"'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test<'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test>'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test|'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test,'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test#'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.prefix'} | ${'test*'} | ${400} | ${"[request body.cron.prefix]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.apis'} | ${['test']} | ${200} | ${null} + ${'cron.statistics.apis'} | ${['test ']} | ${400} | ${"[request body.cron.statistics.apis.0]: No whitespaces allowed."} + ${'cron.statistics.apis'} | ${['']} | ${400} | ${"[request body.cron.statistics.apis.0]: Value can not be empty."} + ${'cron.statistics.apis'} | ${['test', 4]} | ${400} | ${"[request body.cron.statistics.apis.1]: expected value of type [string] but got [number]"} + ${'cron.statistics.apis'} | ${'test space'} | ${400} | ${"[request body.cron.statistics.apis]: could not parse array value from json input"} + ${'cron.statistics.apis'} | ${true} | ${400} | ${"[request body.cron.statistics.apis]: expected value of type [array] but got [boolean]"} + ${'cron.statistics.index.creation'} | ${'h'} | ${200} | ${null} + ${'cron.statistics.index.creation'} | ${'d'} | ${200} | ${null} + ${'cron.statistics.index.creation'} | ${'w'} | ${200} | ${null} + ${'cron.statistics.index.creation'} | ${'m'} | ${200} | ${null} + ${'cron.statistics.index.creation'} | ${'test'} | ${400} | ${"[request body.cron.statistics.index.creation]: types that failed validation:\n- [request body.cron.statistics.index.creation.0]: expected value to equal [h]\n- [request body.cron.statistics.index.creation.1]: expected value to equal [d]\n- [request body.cron.statistics.index.creation.2]: expected value to equal [w]\n- [request body.cron.statistics.index.creation.3]: expected value to equal [m]"} + ${'cron.statistics.index.name'} | ${'test'} | ${200} | ${null} + ${'cron.statistics.index.name'} | ${''} | ${400} | ${"[request body.cron.statistics.index.name]: Value can not be empty."} + ${'cron.statistics.index.name'} | ${'test space'} | ${400} | ${"[request body.cron.statistics.index.name]: No whitespaces allowed."} + ${'cron.statistics.index.name'} | ${true} | ${400} | ${"[request body.cron.statistics.index.name]: expected value of type [string] but got [boolean]"} + ${'cron.statistics.index.name'} | ${'-test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'_test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'+test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'.test'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't start with: -, _, +, .."} + ${'cron.statistics.index.name'} | ${'test\\'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test/'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test?'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test"'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test<'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test>'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test|'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test,'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test#'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.name'} | ${'test*'} | ${400} | ${"[request body.cron.statistics.index.name]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #, *."} + ${'cron.statistics.index.replicas'} | ${0} | ${200} | ${null} + ${'cron.statistics.index.replicas'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.replicas]: Value should be greater or equal than 0."} + ${'cron.statistics.index.replicas'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.index.replicas]: expected value of type [number] but got [string]"} + ${'cron.statistics.index.replicas'} | ${1.2} | ${400} | ${"[request body.cron.statistics.index.replicas]: Number should be an integer."} + ${'cron.statistics.index.shards'} | ${1} | ${200} | ${null} + ${'cron.statistics.index.shards'} | ${-1} | ${400} | ${"[request body.cron.statistics.index.shards]: Value should be greater or equal than 1."} + ${'cron.statistics.index.shards'} | ${1.2} | ${400} | ${"[request body.cron.statistics.index.shards]: Number should be an integer."} + ${'cron.statistics.interval'} | ${'0 */5 * * * *'} | ${200} | ${null} + ${'cron.statistics.interval'} | ${'0 */5 * * *'} | ${200} | ${null} + ${'cron.statistics.interval'} | ${'custom'} | ${400} | ${"[request body.cron.statistics.interval]: Interval is not valid."} + ${'cron.statistics.interval'} | ${true} | ${400} | ${"[request body.cron.statistics.interval]: expected value of type [string] but got [boolean]"} + ${'cron.statistics.status'} | ${true} | ${200} | ${null} + ${'cron.statistics.status'} | ${0} | ${400} | ${'[request body.cron.statistics.status]: expected value of type [boolean] but got [number]'} + ${'customization.reports.footer'} | ${'Test'} | ${200} | ${null} + ${'customization.reports.footer'} | ${'Test\nTest'} | ${200} | ${null} + ${'customization.reports.footer'} | ${'Test\nTest\nTest\nTest\nTest'} | ${400} | ${"[request body.customization.reports.footer]: The string should have less or equal to 2 line/s."} + ${'customization.reports.footer'} | ${'Line with 30 characters \nTest'} | ${200} | ${undefined} + ${'customization.reports.footer'} | ${'Line with 31 characters \nTest'} | ${400} | ${"[request body.customization.reports.footer]: The maximum length of a line is 30 characters."} + ${'customization.reports.footer'} | ${true} | ${400} | ${'[request body.customization.reports.footer]: expected value of type [string] but got [boolean]'} + ${'customization.reports.header'} | ${'Test'} | ${200} | ${null} + ${'customization.reports.header'} | ${'Test\nTest'} | ${200} | ${null} + ${'customization.reports.header'} | ${'Test\nTest\nTest\nTest\nTest'} | ${400} | ${"[request body.customization.reports.header]: The string should have less or equal to 3 line/s."} + ${'customization.reports.header'} | ${'Line with 20 charact\nTest'} | ${200} | ${undefined} + ${'customization.reports.header'} | ${'Line with 23 characters\nTest'} | ${400} | ${"[request body.customization.reports.header]: The maximum length of a line is 20 characters."} + ${'customization.reports.header'} | ${true} | ${400} | ${'[request body.customization.reports.header]: expected value of type [string] but got [boolean]'} + ${'disabled_roles'} | ${['test']} | ${200} | ${null} + ${'disabled_roles'} | ${['']} | ${400} | ${'[request body.disabled_roles.0]: Value can not be empty.'} + ${'disabled_roles'} | ${['test space']} | ${400} | ${"[request body.disabled_roles.0]: No whitespaces allowed."} + ${'disabled_roles'} | ${['test', 4]} | ${400} | ${"[request body.disabled_roles.1]: expected value of type [string] but got [number]"} + ${'enrollment.dns'} | ${'test'} | ${200} | ${null} + ${'enrollment.dns'} | ${''} | ${200} | ${null} + ${'enrollment.dns'} | ${'test space'} | ${400} | ${"[request body.enrollment.dns]: No whitespaces allowed."} + ${'enrollment.dns'} | ${true} | ${400} | ${'[request body.enrollment.dns]: expected value of type [string] but got [boolean]'} + ${'enrollment.password'} | ${'test'} | ${200} | ${null} + ${'enrollment.password'} | ${''} | ${400} | ${"[request body.enrollment.password]: Value can not be empty."} + ${'enrollment.password'} | ${'test space'} | ${200} | ${null} + ${'enrollment.password'} | ${true} | ${400} | ${'[request body.enrollment.password]: expected value of type [string] but got [boolean]'} + ${'extensions.audit'} | ${true} | ${200} | ${null} + ${'extensions.audit'} | ${0} | ${400} | ${'[request body.extensions.audit]: expected value of type [boolean] but got [number]'} + ${'extensions.aws'} | ${true} | ${200} | ${null} + ${'extensions.aws'} | ${0} | ${400} | ${'[request body.extensions.aws]: expected value of type [boolean] but got [number]'} + ${'extensions.ciscat'} | ${true} | ${200} | ${null} + ${'extensions.ciscat'} | ${0} | ${400} | ${'[request body.extensions.ciscat]: expected value of type [boolean] but got [number]'} + ${'extensions.gcp'} | ${true} | ${200} | ${null} + ${'extensions.gcp'} | ${0} | ${400} | ${'[request body.extensions.gcp]: expected value of type [boolean] but got [number]'} + ${'extensions.gdpr'} | ${true} | ${200} | ${null} + ${'extensions.gdpr'} | ${0} | ${400} | ${'[request body.extensions.gdpr]: expected value of type [boolean] but got [number]'} + ${'extensions.hipaa'} | ${true} | ${200} | ${null} + ${'extensions.hipaa'} | ${0} | ${400} | ${'[request body.extensions.hipaa]: expected value of type [boolean] but got [number]'} + ${'extensions.nist'} | ${true} | ${200} | ${null} + ${'extensions.nist'} | ${0} | ${400} | ${'[request body.extensions.nist]: expected value of type [boolean] but got [number]'} + ${'extensions.oscap'} | ${true} | ${200} | ${null} + ${'extensions.oscap'} | ${0} | ${400} | ${'[request body.extensions.oscap]: expected value of type [boolean] but got [number]'} + ${'extensions.osquery'} | ${true} | ${200} | ${null} + ${'extensions.osquery'} | ${0} | ${400} | ${'[request body.extensions.osquery]: expected value of type [boolean] but got [number]'} + ${'extensions.pci'} | ${true} | ${200} | ${null} + ${'extensions.pci'} | ${0} | ${400} | ${'[request body.extensions.pci]: expected value of type [boolean] but got [number]'} + ${'extensions.tsc'} | ${true} | ${200} | ${null} + ${'extensions.tsc'} | ${0} | ${400} | ${'[request body.extensions.tsc]: expected value of type [boolean] but got [number]'} + ${'extensions.virustotal'} | ${true} | ${200} | ${null} + ${'extensions.virustotal'} | ${0} | ${400} | ${'[request body.extensions.virustotal]: expected value of type [boolean] but got [number]'} + ${'ip.ignore'} | ${['test']} | ${200} | ${null} + ${'ip.ignore'} | ${['test*']} | ${200} | ${null} + ${'ip.ignore'} | ${['']} | ${400} | ${'[request body.ip.ignore.0]: Value can not be empty.'} + ${'ip.ignore'} | ${['test space']} | ${400} | ${"[request body.ip.ignore.0]: No whitespaces allowed."} + ${'ip.ignore'} | ${true} | ${400} | ${"[request body.ip.ignore]: expected value of type [array] but got [boolean]"} + ${'ip.ignore'} | ${['-test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['_test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['+test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['.test']} | ${400} | ${"[request body.ip.ignore.0]: It can't start with: -, _, +, .."} + ${'ip.ignore'} | ${['test\\']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test/']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test?']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test"']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test<']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test>']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test|']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test,']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test#']} | ${400} | ${"[request body.ip.ignore.0]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.ignore'} | ${['test', 'test#']} | ${400} | ${"[request body.ip.ignore.1]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'ip.selector'} | ${true} | ${200} | ${null} + ${'ip.selector'} | ${''} | ${400} | ${'[request body.ip.selector]: expected value of type [boolean] but got [string]'} + ${'logs.level'} | ${'info'} | ${200} | ${null} + ${'logs.level'} | ${'debug'} | ${200} | ${null} + ${'logs.level'} | ${''} | ${400} | ${'[request body.logs.level]: types that failed validation:\n- [request body.logs.level.0]: expected value to equal [info]\n- [request body.logs.level.1]: expected value to equal [debug]'} + ${'pattern'} | ${'test'} | ${200} | ${null} + ${'pattern'} | ${'test*'} | ${200} | ${null} + ${'pattern'} | ${''} | ${400} | ${'[request body.pattern]: Value can not be empty.'} + ${'pattern'} | ${'test space'} | ${400} | ${"[request body.pattern]: No whitespaces allowed."} + ${'pattern'} | ${true} | ${400} | ${'[request body.pattern]: expected value of type [string] but got [boolean]'} + ${'pattern'} | ${'-test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'_test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'+test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'.test'} | ${400} | ${"[request body.pattern]: It can't start with: -, _, +, .."} + ${'pattern'} | ${'test\\'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test/'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test?'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test"'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test<'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test>'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test|'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test,'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'pattern'} | ${'test#'} | ${400} | ${"[request body.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'timeout'} | ${15000} | ${200} | ${null} + ${'timeout'} | ${1000} | ${400} | ${'[request body.timeout]: Value should be greater or equal than 1500.'} + ${'timeout'} | ${''} | ${400} | ${'[request body.timeout]: expected value of type [number] but got [string]'} + ${'timeout'} | ${1.2} | ${400} | ${"[request body.timeout]: Number should be an integer."} + ${'wazuh.monitoring.creation'} | ${'h'} | ${200} | ${null} + ${'wazuh.monitoring.creation'} | ${'d'} | ${200} | ${null} + ${'wazuh.monitoring.creation'} | ${'w'} | ${200} | ${null} + ${'wazuh.monitoring.creation'} | ${'m'} | ${200} | ${null} + ${'wazuh.monitoring.creation'} | ${'test'} | ${400} | ${"[request body.wazuh.monitoring.creation]: types that failed validation:\n- [request body.wazuh.monitoring.creation.0]: expected value to equal [h]\n- [request body.wazuh.monitoring.creation.1]: expected value to equal [d]\n- [request body.wazuh.monitoring.creation.2]: expected value to equal [w]\n- [request body.wazuh.monitoring.creation.3]: expected value to equal [m]"} + ${'wazuh.monitoring.enabled'} | ${true} | ${200} | ${null} + ${'wazuh.monitoring.enabled'} | ${0} | ${400} | ${'[request body.wazuh.monitoring.enabled]: expected value of type [boolean] but got [number]'} + ${'wazuh.monitoring.frequency'} | ${100} | ${200} | ${null} + ${'wazuh.monitoring.frequency'} | ${40} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Value should be greater or equal than 60."} + ${'wazuh.monitoring.frequency'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.frequency]: Number should be an integer."} + ${'wazuh.monitoring.frequency'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.frequency]: expected value of type [number] but got [string]'} + ${'wazuh.monitoring.pattern'} | ${'test'} | ${200} | ${null} + ${'wazuh.monitoring.pattern'} | ${'test*'} | ${200} | ${null} + ${'wazuh.monitoring.pattern'} | ${''} | ${400} | ${'[request body.wazuh.monitoring.pattern]: value has length [0] but it must have a minimum length of [1].'} + ${'wazuh.monitoring.pattern'} | ${true} | ${400} | ${'[request body.wazuh.monitoring.pattern]: expected value of type [string] but got [boolean]'} + ${'wazuh.monitoring.pattern'} | ${'-test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'_test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'+test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'.test'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't start with: -, _, +, .."} + ${'wazuh.monitoring.pattern'} | ${'test\\'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test/'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test?'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test"'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test<'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test>'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test|'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test,'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.pattern'} | ${'test#'} | ${400} | ${"[request body.wazuh.monitoring.pattern]: It can't contain invalid characters: \\, /, ?, \", <, >, |, ,, #."} + ${'wazuh.monitoring.replicas'} | ${0} | ${200} | ${null} + ${'wazuh.monitoring.replicas'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Value should be greater or equal than 0."} + ${'wazuh.monitoring.replicas'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.replicas]: Number should be an integer."} + ${'wazuh.monitoring.replicas'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.replicas]: expected value of type [number] but got [string]"} + ${'wazuh.monitoring.shards'} | ${1} | ${200} | ${null} + ${'wazuh.monitoring.shards'} | ${-1} | ${400} | ${"[request body.wazuh.monitoring.shards]: Value should be greater or equal than 1."} + ${'wazuh.monitoring.shards'} | ${1.2} | ${400} | ${"[request body.wazuh.monitoring.shards]: Number should be an integer."} + ${'wazuh.monitoring.shards'} | ${'custom'} | ${400} | ${"[request body.wazuh.monitoring.shards]: expected value of type [number] but got [string]"} `(`$setting: $value - PUT /utils/configuration - $responseStatusCode`, async ({ responseBodyMessage, responseStatusCode, setting, value }) => { const body = { [setting]: value }; const response = await supertest(innerServer.listener) From 5a40771f757cd00d7b0bc5b8e19eedbd2db4d956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 31 Oct 2022 09:28:55 +0100 Subject: [PATCH 78/79] changelog: add PR entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcedf93b5..a8d1112fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Redesign the SCA table from agent's dashboard [#4512](https://github.com/wazuh/wazuh-kibana-app/pull/4512) - Enhanced the plugin setting description displayed in the UI and the configuration file. [#4501](https://github.com/wazuh/wazuh-kibana-app/pull/4501) - Added validation to the plugin settings in the form of `Settings/Configuration` and the endpoint to update the plugin configuration [#4503](https://github.com/wazuh/wazuh-kibana-app/pull/4503) +- Added new plugin settings to customize the header and footer on the PDF reports [#4505](https://github.com/wazuh/wazuh-kibana-app/pull/4505) ### Changed From cf3b96776018752cbbd693bc5e35c2ed24b3b5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 31 Oct 2022 09:59:48 +0100 Subject: [PATCH 79/79] test(settings): format tests --- server/routes/wazuh-reporting.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/routes/wazuh-reporting.test.ts b/server/routes/wazuh-reporting.test.ts index 8b1b9b2ccd..ae8f52de63 100644 --- a/server/routes/wazuh-reporting.test.ts +++ b/server/routes/wazuh-reporting.test.ts @@ -174,12 +174,12 @@ describe('[endpoint] PUT /utils/configuration', () => { // expectedMD5 variable is a verified md5 of a report generated with this header and footer // If any of the parameters is changed this variable should be updated with the new md5 it.each` - footer | header | responseStatusCode | expectedMD5 | tab - ${null} | ${null} | ${200} | ${'1bdc0cc05cc79fdfbb9b734a4e1cc07b'} | ${'pm'} - ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'}| ${200} | ${'0acbd4ee321699791b080b45c11dfe2b'} | ${'general'} - ${''} | ${''} | ${200} | ${'5f9e16540e9d8109bed75b2a8f825164'} | ${'fim'} - ${'Custom Footer'} | ${null} | ${200} | ${'5bd4c559419028fd0d282c5e8408ecff'} | ${'aws'} - ${null} | ${'Custom Header'} | ${200} | ${'f6bfca395bc3fc78105f66120f336443'} | ${'gcp'} + footer | header | responseStatusCode | expectedMD5 | tab + ${null} | ${null} | ${200} | ${'1bdc0cc05cc79fdfbb9b734a4e1cc07b'} | ${'pm'} + ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'}| ${200} | ${'0acbd4ee321699791b080b45c11dfe2b'} | ${'general'} + ${''} | ${''} | ${200} | ${'5f9e16540e9d8109bed75b2a8f825164'} | ${'fim'} + ${'Custom Footer'} | ${null} | ${200} | ${'5bd4c559419028fd0d282c5e8408ecff'} | ${'aws'} + ${null} | ${'Custom Header'} | ${200} | ${'f6bfca395bc3fc78105f66120f336443'} | ${'gcp'} `(`Set custom report header and footer - Verify PDF output`, async ({footer, header, responseStatusCode, expectedMD5, tab}) => { // Mock PDF report parameters