diff --git a/web/src/client/storage.js b/web/src/client/storage.js index 7290b74bc3..338d7af153 100644 --- a/web/src/client/storage.js +++ b/web/src/client/storage.js @@ -227,6 +227,7 @@ class ProposalManager { * @typedef {object} ProposalSettings * @property {string} bootDevice * @property {string} encryptionPassword + * @property {string} encryptionMethod * @property {boolean} lvm * @property {string} spacePolicy * @property {string[]} systemVGDevices @@ -283,6 +284,16 @@ class ProposalManager { return proxy.ProductMountPoints; } + /** + * Gets the list of encryption methods accepted by the proposal + * + * @returns {Promise} + */ + async getEncryptionMethods() { + const proxy = await this.proxies.proposalCalculator; + return proxy.EncryptionMethods; + } + /** * Obtains the default volume for the given mount path * @@ -345,6 +356,7 @@ class ProposalManager { spacePolicy: proxy.SpacePolicy, systemVGDevices: proxy.SystemVGDevices, encryptionPassword: proxy.EncryptionPassword, + encryptionMethod: proxy.EncryptionMethod, volumes: proxy.Volumes.map(this.buildVolume), // NOTE: strictly speaking, installation devices does not belong to the settings. It // should be a separate method instead of an attribute in the settings object. @@ -365,7 +377,7 @@ class ProposalManager { * @param {ProposalSettings} settings * @returns {Promise} 0 on success, 1 on failure */ - async calculate({ bootDevice, encryptionPassword, lvm, spacePolicy, systemVGDevices, volumes }) { + async calculate({ bootDevice, encryptionPassword, encryptionMethod, lvm, spacePolicy, systemVGDevices, volumes }) { const dbusVolume = (volume) => { return removeUndefinedCockpitProperties({ MountPath: { t: "s", v: volume.mountPath }, @@ -381,6 +393,7 @@ class ProposalManager { const settings = removeUndefinedCockpitProperties({ BootDevice: { t: "s", v: bootDevice }, EncryptionPassword: { t: "s", v: encryptionPassword }, + EncryptionMethod: { t: "s", v: encryptionMethod }, LVM: { t: "b", v: lvm }, SpacePolicy: { t: "s", v: spacePolicy }, SystemVGDevices: { t: "as", v: systemVGDevices }, diff --git a/web/src/components/storage/ProposalPage.jsx b/web/src/components/storage/ProposalPage.jsx index 335c939167..4680d9cf1a 100644 --- a/web/src/components/storage/ProposalPage.jsx +++ b/web/src/components/storage/ProposalPage.jsx @@ -54,6 +54,11 @@ const reducer = (state, action) => { return { ...state, availableDevices }; } + case "UPDATE_ENCRYPTION_METHODS": { + const { encryptionMethods } = action.payload; + return { ...state, encryptionMethods }; + } + case "UPDATE_VOLUME_TEMPLATES": { const { volumeTemplates } = action.payload; return { ...state, volumeTemplates }; @@ -89,6 +94,10 @@ export default function ProposalPage() { return await cancellablePromise(client.proposal.getAvailableDevices()); }, [client, cancellablePromise]); + const loadEncryptionMethods = useCallback(async () => { + return await cancellablePromise(client.proposal.getEncryptionMethods()); + }, [client, cancellablePromise]); + const loadVolumeTemplates = useCallback(async () => { const mountPoints = await cancellablePromise(client.proposal.getProductMountPoints()); const volumeTemplates = []; @@ -127,6 +136,9 @@ export default function ProposalPage() { const availableDevices = await loadAvailableDevices(); dispatch({ type: "UPDATE_AVAILABLE_DEVICES", payload: { availableDevices } }); + const encryptionMethods = await loadEncryptionMethods(); + dispatch({ type: "UPDATE_ENCRYPTION_METHODS", payload: { encryptionMethods } }); + const volumeTemplates = await loadVolumeTemplates(); dispatch({ type: "UPDATE_VOLUME_TEMPLATES", payload: { volumeTemplates } }); @@ -137,7 +149,7 @@ export default function ProposalPage() { dispatch({ type: "UPDATE_ERRORS", payload: { errors } }); if (result !== undefined) dispatch({ type: "STOP_LOADING" }); - }, [calculateProposal, cancellablePromise, client, loadAvailableDevices, loadErrors, loadProposalResult, loadVolumeTemplates]); + }, [calculateProposal, cancellablePromise, client, loadAvailableDevices, loadEncryptionMethods, loadErrors, loadProposalResult, loadVolumeTemplates]); const calculate = useCallback(async (settings) => { dispatch({ type: "START_LOADING" }); @@ -200,6 +212,7 @@ export default function ProposalPage() { { const [password, setPassword] = useState(passwordProp || ""); + const [method, setMethod] = useState(methodProp); + const tpmId = "tpm_fde"; + const luks2Id = "luks2"; useEffect(() => { if (password.length === 0) onValidate(false); @@ -399,9 +404,13 @@ const EncryptionPasswordForm = ({ const changePassword = (_, v) => setPassword(v); + const changeMethod = (_, value) => { + value ? setMethod(tpmId) : setMethod(luks2Id); + }; + const submitForm = (e) => { e.preventDefault(); - onSubmit(password); + onSubmit(password, method); }; return ( @@ -412,16 +421,29 @@ const EncryptionPasswordForm = ({ onChange={changePassword} onValidation={onValidate} /> + + } + /> ); }; /** - * Allows to selected encryption + * Allows to define encryption * @component * * @param {object} props * @param {string} [props.password=""] - Password for encryption + * @param {string} [props.method=""] - Encryption method * @param {boolean} [props.isChecked=false] - Whether encryption is selected * @param {boolean} [props.isLoading=false] - Whether to show the selector as loading * @param {onChangeFn} [props.onChange=noop] - On change callback @@ -429,14 +451,17 @@ const EncryptionPasswordForm = ({ * @callback onChangeFn * @param {object} settings */ -const EncryptionPasswordField = ({ +const EncryptionField = ({ password: passwordProp = "", + method: methodProp = "", + methods, isChecked: isCheckedProp = false, isLoading = false, onChange = noop }) => { const [isChecked, setIsChecked] = useState(isCheckedProp); const [password, setPassword] = useState(passwordProp); + const [method, setMethod] = useState(methodProp); const [isFormOpen, setIsFormOpen] = useState(false); const [isFormValid, setIsFormValid] = useState(true); @@ -444,10 +469,11 @@ const EncryptionPasswordField = ({ const closeForm = () => setIsFormOpen(false); - const acceptForm = (newPassword) => { + const acceptForm = (newPassword, newMethod) => { closeForm(); setPassword(newPassword); - onChange({ isChecked, password: newPassword }); + setMethod(newMethod); + onChange({ isChecked, password: newPassword, method: newMethod }); }; const cancelForm = () => { @@ -468,10 +494,10 @@ const EncryptionPasswordField = ({ } }; - const ChangePasswordButton = () => { + const ChangeSettingsButton = () => { return ( - { isChecked && } + { isChecked && } - - {_("Accept")} + {_("Accept")} @@ -618,6 +646,7 @@ const SpacePolicyField = ({ * @param {ProposalSettings} props.settings * @param {StorageDevice[]} [props.availableDevices=[]] * @param {Volume[]} [props.volumeTemplates=[]] + * @param {String[]} [props.encryptionMethods=[]] * @param {boolean} [isLoading=false] * @param {onChangeFn} [props.onChange=noop] * @@ -628,6 +657,7 @@ export default function ProposalSettingsSection({ settings, availableDevices = [], volumeTemplates = [], + encryptionMethods = [], isLoading = false, onChange = noop }) { @@ -643,8 +673,8 @@ export default function ProposalSettingsSection({ onChange(settings); }; - const changeEncryption = ({ password }) => { - onChange({ encryptionPassword: password }); + const changeEncryption = ({ password, method }) => { + onChange({ encryptionPassword: password, encryptionMethod: method }); }; const changeSpacePolicy = (policy) => { @@ -673,8 +703,10 @@ export default function ProposalSettingsSection({ isLoading={settings.lvm === undefined} onChange={changeLVM} /> -