From 37152c258923b87998f690edc248247353bfac17 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 30 Jun 2022 11:30:26 +0300 Subject: [PATCH] UI: Change configuration import to validate and decrypt on UI --- .../ImportConfigModal.tsx | 93 +++++++++++-------- .../ui/src/components/pages/ConfigurePage.js | 1 + 2 files changed, 57 insertions(+), 37 deletions(-) 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 70e366d20..872e1c002 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 @@ -9,10 +9,12 @@ import UnsafeConfigOptionsConfirmationModal from './UnsafeConfigOptionsConfirmationModal.js'; import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; +import {decryptText} from '../utils/PasswordBasedEncryptor'; type Props = { show: boolean, + schema: object, onClose: (importSuccessful: boolean) => void } @@ -23,9 +25,9 @@ const ConfigImportModal = (props: Props) => { const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); const [configContents, setConfigContents] = useState(null); - const [candidateConfig, setCandidateConfig] = useState(null); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); + const [configEncrypted, setConfigEncrypted] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [unsafeOptionsVerified, setUnsafeOptionsVerified] = useState(false); const [showUnsafeOptionsConfirmation, @@ -36,10 +38,33 @@ const ConfigImportModal = (props: Props) => { useEffect(() => { if (configContents !== null) { - sendConfigToServer(); + saveConfig(); } }, [configContents, unsafeOptionsVerified]) + function saveConfig() { + if (configEncrypted && !showPassword){ + setShowPassword(true); + } else if (configEncrypted && showPassword) { + try { + let decryptedConfig = JSON.parse(decryptText(configContents, password)); + setConfigEncrypted(false); + setConfigContents(decryptedConfig); + } catch (e) { + setUploadStatus(UploadStatuses.error); + setErrorMessage('Decryption failed: Password is wrong or the file is corrupted'); + } + } else if(!unsafeOptionsVerified) { + if(isUnsafeOptionSelected(props.schema, configContents)){ + setShowUnsafeOptionsConfirmation(true); + } else { + setUnsafeOptionsVerified(true); + } + } else { + sendConfigToServer(); + setUploadStatus(UploadStatuses.success); + } + } function sendConfigToServer() { authComponent.authFetch(configImportEndpoint, @@ -48,34 +73,18 @@ const ConfigImportModal = (props: Props) => { headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ config: configContents, - password: password, - unsafeOptionsVerified: unsafeOptionsVerified }) } ).then(res => res.json()) .then(res => { - if (res['import_status'] === 'invalid_credentials') { - setUploadStatus(UploadStatuses.success); - if (showPassword){ - setErrorMessage(res['message']); - } else { + if (res['import_status'] === 'invalid_configuration') { + if(showPassword){ setShowPassword(true); - setErrorMessage(''); - } - } else if (res['import_status'] === 'invalid_configuration') { - setUploadStatus(UploadStatuses.error); - setErrorMessage(res['message']); - } else if (res['import_status'] === 'unsafe_options_verification_required') { - setUploadStatus(UploadStatuses.success); - setErrorMessage(''); - - if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) { - setShowUnsafeOptionsConfirmation(true); - setCandidateConfig(res['config']); } else { - setUnsafeOptionsVerified(true); + setUploadStatus(UploadStatuses.error); + setErrorMessage(res['message']); } - } else if (res['import_status'] === 'imported'){ + } else if (res['import_status'] === 'imported') { resetState(); props.onClose(true); } @@ -83,7 +92,8 @@ const ConfigImportModal = (props: Props) => { } function isImportDisabled(): boolean { - return uploadStatus !== UploadStatuses.success || (showPassword && password === '') + // Don't allow import if password input is empty or there's an error + return (showPassword && password === '') || (errorMessage !== ''); } function resetState() { @@ -95,13 +105,22 @@ const ConfigImportModal = (props: Props) => { setShowUnsafeOptionsConfirmation(false); setUnsafeOptionsVerified(false); setFileFieldKey(Date.now()); // Resets the file input + setConfigEncrypted(false); } function uploadFile(event) { setShowPassword(false); let reader = new FileReader(); reader.onload = (event) => { - setConfigContents(event.target.result); + let importContents = null; + try { + importContents = JSON.parse(event.target.result); + } catch (e){ + setErrorMessage("File is not in a valid json format"); + return + } + setConfigEncrypted(importContents['metadata']['encrypted']); + setConfigContents(importContents['contents']); }; reader.readAsText(event.target.files[0]); } @@ -115,7 +134,6 @@ const ConfigImportModal = (props: Props) => { }} onContinueClick={() => { setUnsafeOptionsVerified(true); - setConfigContents(candidateConfig); }} /> ); @@ -142,28 +160,29 @@ const ConfigImportModal = (props: Props) => {
{showVerificationDialog()}
- - {showPassword && } + {showPassword && {setPassword(password); + setErrorMessage('')}}/>} {errorMessage && - - - {errorMessage} - + + + {errorMessage} + }
@@ -177,8 +196,8 @@ const PasswordInput = (props: { return (

File is protected. Please enter the password:

- (props.onChange(evt.target.value))}/>
) 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 b2909afd5..b59659ba8 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -180,6 +180,7 @@ class ConfigurePageComponent extends AuthComponent { renderConfigImportModal = () => { return (); }