diff --git a/monkey/monkey_island/cc/resources/agent_configuration.py b/monkey/monkey_island/cc/resources/agent_configuration.py index 0f9279bba..47be16abc 100644 --- a/monkey/monkey_island/cc/resources/agent_configuration.py +++ b/monkey/monkey_island/cc/resources/agent_configuration.py @@ -24,7 +24,7 @@ class AgentConfiguration(AbstractResource): @jwt_required def post(self): try: - configuration_object = AgentConfigurationObject.from_json(request.data) + configuration_object = AgentConfigurationObject.from_mapping(request.json) self._agent_configuration_repository.store_configuration(configuration_object) return make_response({}, 200) except (InvalidConfigurationError, json.JSONDecodeError) as err: diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index 469d91ec3..ef11dd67f 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -4,6 +4,7 @@ import React, {useState} from 'react'; import FileSaver from 'file-saver'; import '../../styles/components/configuration-components/ExportConfigModal.scss'; import {encryptText} from '../utils/PasswordBasedEncryptor'; +import {reformatConfig} from './ReformatHook'; type Props = { @@ -21,7 +22,7 @@ const ConfigExportModal = (props: Props) => { } function onSubmit() { - let config = props.configuration; + let config = reformatConfig(props.configuration, true); let config_export = {'metadata': {}, 'contents': null}; if (radioValue === 'password') { diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index a60a25c0f..cce66a521 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -20,8 +20,7 @@ type Props = { const ConfigImportModal = (props: Props) => { - // TODO: change this endpoint to the new configuration import endpoint - const configImportEndpoint = '/api/configuration/import'; + const configImportEndpoint = '/api/agent-configuration'; const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); const [configContents, setConfigContents] = useState(null); @@ -71,18 +70,15 @@ const ConfigImportModal = (props: Props) => { { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - config: configContents, - }) + body: JSON.stringify(configContents) } - ).then(res => res.json()) - .then(res => { - if (res['import_status'] === 'invalid_configuration') { - setUploadStatus(UploadStatuses.error); - setErrorMessage(res['message']); - } else if (res['import_status'] === 'imported') { + ).then(res => { + if (res.ok) { resetState(); props.onClose(true); + } else { + setUploadStatus(UploadStatuses.error); + setErrorMessage("Configuration file is corrupt or in an outdated format."); } }) } diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js new file mode 100644 index 000000000..322882194 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js @@ -0,0 +1,8 @@ +export function reformatConfig(config, reverse = false) { + if (reverse) { + config['payloads'] = [{'name': 'ransomware', 'options': config['payloads']}] + } else { + config['payloads'] = config['payloads'][0]['options']; + } + return config; + } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 8bcad002b..154eff52c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -1,6 +1,6 @@ import React from 'react'; import Form from 'react-jsonschema-form-bs4'; -import {Button, Col, Modal, Nav} from 'react-bootstrap'; +import {Col, Nav} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import UiSchema from '../configuration-components/UiSchema'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; @@ -10,7 +10,6 @@ import {formValidationFormats} from '../configuration-components/ValidationForma import transformErrors from '../configuration-components/ValidationErrorMessages'; import UnsafeConfigOptionsConfirmationModal from '../configuration-components/UnsafeConfigOptionsConfirmationModal.js'; -import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import ConfigExportModal from '../configuration-components/ExportConfigModal'; import ConfigImportModal from '../configuration-components/ImportConfigModal'; @@ -18,8 +17,10 @@ import applyUiSchemaManipulators from '../configuration-components/UISchemaManip import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js'; import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js'; import {SCHEMA} from '../../services/configuration/config_schema.js'; +import {reformatConfig} from '../configuration-components/ReformatHook'; -const CONFIG_URL = '/api/configuration/island'; +const CONFIG_URL = '/api/agent-configuration'; +const RESET_URL = '/api/reset-agent-configuration'; export const API_PBA_LINUX = '/api/file-upload/PBAlinux'; export const API_PBA_WINDOWS = '/api/file-upload/PBAwindows'; @@ -42,9 +43,7 @@ class ConfigurePageComponent extends AuthComponent { schema: {}, sections: [], selectedSection: this.currentSection, - showUnsubmittedConfigWarning: false, showUnsafeOptionsConfirmation: false, - showUnsafeAttackOptionsWarning: false, showConfigExportModal: false, showConfigImportModal: false }; @@ -57,8 +56,13 @@ class ConfigurePageComponent extends AuthComponent { } } + resetLastAction = () => { + this.setState({lastAction: 'none'}); + } + getSectionsOrder() { - let islandMode = this.props.islandMode !== 'unset' ? this.props.islandMode : 'advanced' + let islandModeSet = (this.props.islandMode !== 'unset' && this.props.islandMode !== undefined) + let islandMode = islandModeSet ? this.props.islandMode : 'advanced' return CONFIGURATION_TABS_PER_MODE[islandMode]; } @@ -68,14 +72,10 @@ class ConfigurePageComponent extends AuthComponent { } componentDidMount = () => { - let urls = ['/api/agent-configuration']; - // ??? Why fetch config here and not in `render()`? - Promise.all(urls.map(url => this.authFetch(url).then(res => res.json()))) - .then(data => { + this.authFetch(CONFIG_URL).then(res => res.json()) + .then(monkeyConfig => { let sections = []; - let monkeyConfig = data[0]; - // TODO: Fix when we add plugins - monkeyConfig['payloads'] = monkeyConfig['payloads'][0]['options']; + monkeyConfig = reformatConfig(monkeyConfig); this.setInitialConfig(monkeyConfig); for (let sectionKey of this.getSectionsOrder()) { @@ -108,17 +108,16 @@ class ConfigurePageComponent extends AuthComponent { } } - onUnsafeAttackContinueClick = () => { - this.setState({showUnsafeAttackOptionsWarning: false}); - } - - updateConfig = (callback = null) => { + updateConfig = () => { this.authFetch(CONFIG_URL) .then(res => res.json()) .then(data => { - this.setInitialConfig(data.configuration); - this.setState({configuration: data.configuration, - currentFormData: data.configuration[this.state.selectedSection]}, callback); + data = reformatConfig(data); + this.setInitialConfig(data); + this.setState({ + configuration: data, + currentFormData: data[this.state.selectedSection] + }); }) }; @@ -134,7 +133,7 @@ class ConfigurePageComponent extends AuthComponent { await this.updateConfigSection(); if (this.canSafelySubmitConfig(this.state.configuration)) { this.configSubmit(); - if(this.state.lastAction === configExportAction){ + if (this.state.lastAction === configExportAction) { this.setState({showConfigExportModal: true}) } } else { @@ -145,18 +144,16 @@ class ConfigurePageComponent extends AuthComponent { configSubmit() { return this.sendConfig() .then(res => res.json()) - .then(res => { + .then(() => { this.setState({ - lastAction: configSaveAction, - schema: res.schema, - configuration: res.configuration + lastAction: configSaveAction }); - this.setInitialConfig(res.configuration); + this.setInitialConfig(this.state.configuration); this.props.onStatusChange(); }).catch(error => { - console.log('Bad configuration: ' + error.toString()); - this.setState({lastAction: 'invalid_configuration'}); - }); + console.log('Bad configuration: ' + error.toString()); + this.setState({lastAction: 'invalid_configuration'}); + }); } onChange = ({formData}) => { @@ -189,42 +186,18 @@ class ConfigurePageComponent extends AuthComponent { } onClose = (importSuccessful) => { - if(importSuccessful === true){ + if (importSuccessful === true) { this.updateConfig(); - this.setState({lastAction: 'import_success', - showConfigImportModal: false}); + this.setState({ + lastAction: 'import_success', + showConfigImportModal: false + }); } else { this.setState({showConfigImportModal: false}); } } - renderAttackAlertModal = () => { - return ( { - this.setState({showUnsubmittedConfigWarning: false}) - }}> - -

-
Warning
-

-

- You have unsubmitted changes. Submit them before proceeding. -

-
- -
-
-
) - }; - renderUnsafeOptionsConfirmationModal() { return ( - ); - } - - userChangedConfig() { - try { - if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) { - if (Object.keys(this.state.currentFormData).length === 0 || - JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)) { - return false; - } - } - } catch (TypeError) { - if (JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)){ - return false; - } - } - return true; - } - setSelectedSection = (key) => { - - // TODO: Fix https://github.com/guardicore/monkey/issues/1621 - //if ( key === 'basic' & this.userChangedConfig()) { - // this.setState({showUnsubmittedConfigWarning: true}); - // return; - //} - + this.resetLastAction(); this.updateConfigSection(); this.currentSection = key; let selectedSectionData = this.state.configuration[key]; @@ -279,21 +221,16 @@ class ConfigurePageComponent extends AuthComponent { }; resetConfig = () => { - this.authFetch(CONFIG_URL, + this.authFetch(RESET_URL, { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({'reset': true}) + method: 'POST' }) .then(res => res.json()) - .then(res => { + .then(() => { this.setState({ - lastAction: 'reset', - schema: res.schema, - configuration: res.configuration, - currentFormData: res.configuration[this.state.selectedSection] + lastAction: 'reset' }); - this.setInitialConfig(res.configuration); + this.updateConfig(); this.props.onStatusChange(); } ).then(() => { @@ -321,12 +258,15 @@ class ConfigurePageComponent extends AuthComponent { }; sendConfig() { + let config = JSON.parse(JSON.stringify(this.state.configuration)) + config = reformatConfig(config, true); + return ( - this.authFetch('/api/configuration/island', + this.authFetch(CONFIG_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(this.state.configuration) + body: JSON.stringify(config) }) .then(res => { if (!res.ok) { @@ -352,14 +292,15 @@ class ConfigurePageComponent extends AuthComponent { formProperties['fields'] = {DescriptionField: HtmlFieldDescription}; formProperties['formData'] = this.state.currentFormData; formProperties['onChange'] = this.onChange; + formProperties['onFocus'] = this.resetLastAction; formProperties['customFormats'] = formValidationFormats; formProperties['transformErrors'] = transformErrors; formProperties['className'] = 'config-form'; formProperties['liveValidate'] = true; applyUiSchemaManipulators(this.state.selectedSection, - formProperties['formData'], - formProperties['uiSchema']); + formProperties['formData'], + formProperties['uiSchema']); return (
@@ -419,9 +360,7 @@ class ConfigurePageComponent extends AuthComponent { className={'main'}> {this.renderConfigExportModal()} {this.renderConfigImportModal()} - {this.renderAttackAlertModal()} {this.renderUnsafeOptionsConfirmationModal()} - {this.renderUnsafeAttackOptionsWarningModal()}

Monkey Configuration

{this.renderNav()} {content} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js b/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js index fbef10235..e0bc667de 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/AdvancedMultiSelect.js @@ -182,7 +182,7 @@ class AdvancedMultiSelect extends React.Component { } = this.props; return ( -
+