diff --git a/src/components/AnacondaWizard.jsx b/src/components/AnacondaWizard.jsx
index cbbe264d89..31a09421b6 100644
--- a/src/components/AnacondaWizard.jsx
+++ b/src/components/AnacondaWizard.jsx
@@ -25,25 +25,22 @@ import {
PageSection,
PageSectionTypes,
PageSectionVariants,
- Stack
-} from "@patternfly/react-core";
-import {
+ Stack,
+ useWizardContext,
Wizard,
- WizardFooter,
- WizardContextConsumer
-} from "@patternfly/react-core/deprecated";
+ WizardFooterWrapper,
+ WizardStep
+} from "@patternfly/react-core";
-import { AnacondaPage } from "./AnacondaPage.jsx";
import { InstallationMethod, getPageProps as getInstallationMethodProps } from "./storage/InstallationMethod.jsx";
-import { getDefaultScenario } from "./storage/InstallationScenario.jsx";
+import { getDefaultScenario, getScenario } from "./storage/InstallationScenario.jsx";
import { MountPointMapping, getPageProps as getMountPointMappingProps } from "./storage/MountPointMapping.jsx";
import { DiskEncryption, getStorageEncryptionState, getPageProps as getDiskEncryptionProps } from "./storage/DiskEncryption.jsx";
import { InstallationLanguage, getPageProps as getInstallationLanguageProps } from "./localization/InstallationLanguage.jsx";
-import { Accounts, getPageProps as getAccountsProps, getAccountsState, accountsToDbusUsers } from "./users/Accounts.jsx";
+import { Accounts, getPageProps as getAccountsProps, getAccountsState, accountsToDbusUsers, cryptUserPassword } from "./users/Accounts.jsx";
import { InstallationProgress } from "./installation/InstallationProgress.jsx";
import { ReviewConfiguration, ReviewConfigurationConfirmModal, getPageProps as getReviewConfigurationProps } from "./review/ReviewConfiguration.jsx";
import { exitGui } from "../helpers/exit.js";
-import { usePageLocation } from "hooks";
import {
getRequiredMountPoints,
} from "../apis/storage_devicetree.js";
@@ -55,6 +52,7 @@ import {
setUsers,
} from "../apis/users.js";
import { SystemTypeContext, OsReleaseContext } from "./Common.jsx";
+import { AnacondaPage } from "./AnacondaPage.jsx";
const _ = cockpit.gettext;
const N_ = cockpit.noop;
@@ -172,6 +170,13 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
},
];
+ const componentProps = {
+ isFormDisabled,
+ onCritFail,
+ setIsFormDisabled,
+ setIsFormValid,
+ };
+
const getFlattenedStepsIds = (steps) => {
const stepIds = [];
for (const step of steps) {
@@ -189,63 +194,58 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
};
const flattenedStepsIds = getFlattenedStepsIds(stepsOrder);
- const { path } = usePageLocation();
- const firstStepId = stepsOrder.filter(step => !step.isHidden)[0].id;
- const currentStepId = path[0] || firstStepId;
-
const isStepFollowedBy = (earlierStepId, laterStepId) => {
const earlierStepIdx = flattenedStepsIds.findIndex(s => s === earlierStepId);
const laterStepIdx = flattenedStepsIds.findIndex(s => s === laterStepId);
return earlierStepIdx < laterStepIdx;
};
- const canJumpToStep = (stepId, currentStepId) => {
- return stepId === currentStepId || isStepFollowedBy(stepId, currentStepId);
- };
-
- const createSteps = (stepsOrder) => {
- const steps = stepsOrder.filter(s => !s.isHidden).map(s => {
- let step = ({
+ const createSteps = (stepsOrder, componentProps) => {
+ return stepsOrder.filter(s => !s.isHidden).map(s => {
+ let stepProps = {
id: s.id,
+ isHidden: s.isHidden,
name: s.label,
stepNavItemProps: { id: s.id },
- canJumpTo: canJumpToStep(s.id, currentStepId),
- });
+ ...(s.steps?.length && { isExpandable: true }),
+ };
if (s.component) {
- step = ({
- ...step,
- component: (
+ stepProps = {
+ children: (
setStepNotification({ step: s.id, ...ex })}
- isFormDisabled={isFormDisabled}
- setIsFormDisabled={setIsFormDisabled}
+ {...componentProps}
{...s.data}
/>
),
- });
+ ...stepProps
+ };
} else if (s.steps) {
- step.steps = createSteps(s.steps);
+ const subSteps = createSteps(s.steps, componentProps);
+ stepProps = {
+ ...stepProps,
+ steps: [...subSteps]
+ };
}
- return step;
+ return (
+
+ );
});
- return steps;
};
- const steps = createSteps(stepsOrder);
+ const steps = createSteps(stepsOrder, componentProps);
const goToStep = (newStep, prevStep) => {
- if (prevStep.prevId !== newStep.id) {
+ if (prevStep.id !== newStep.id) {
// first reset validation state to default
setIsFormValid(false);
}
// Reset the applied partitioning when going back from a step after creating partitioning to a step
// before creating partitioning.
- if ((prevStep.prevId === "accounts" || isStepFollowedBy("accounts", prevStep.prevId)) &&
+ if ((prevStep.id === "accounts" || isStepFollowedBy("accounts", prevStep.id)) &&
isStepFollowedBy(newStep.id, "accounts")) {
setIsFormDisabled(true);
resetPartitioning()
@@ -271,6 +271,7 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
}
- hideClose
- mainAriaLabel={`${title} content`}
- navAriaLabel={`${title} steps`}
- onBack={goToStep}
- onGoToStep={goToStep}
- onNext={goToStep}
- steps={steps}
- isNavExpandable
- />
+ onStepChange={((event, currentStep, prevStep) => goToStep(currentStep, prevStep))}
+ >
+ {steps}
+
);
};
@@ -314,9 +310,10 @@ const Footer = ({
}) => {
const [nextWaitsConfirmation, setNextWaitsConfirmation] = useState(false);
const [quitWaitsConfirmation, setQuitWaitsConfirmation] = useState(false);
+ const { activeStep, goToNextStep, goToPrevStep } = useWizardContext();
const isBootIso = useContext(SystemTypeContext) === "BOOT_ISO";
- const goToNextStep = (activeStep, onNext) => {
+ const onNext = (activeStep, goToNextStep) => {
// first reset validation state to default
setIsFormValid(true);
@@ -330,7 +327,7 @@ const Footer = ({
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
- onNext();
+ goToNextStep();
// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
@@ -353,7 +350,7 @@ const Footer = ({
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
- onNext();
+ goToNextStep();
// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
@@ -362,85 +359,88 @@ const Footer = ({
},
});
} else if (activeStep.id === "accounts") {
- setUsers(accountsToDbusUsers(accounts));
- onNext();
+ cryptUserPassword(accounts.password)
+ .then(cryptedPassword => {
+ const users = accountsToDbusUsers({ ...accounts, password: cryptedPassword });
+ setUsers(users);
+ onNext();
+ }, onCritFail({ context: N_("Password ecryption failed.") }));
} else {
- onNext();
+ goToNextStep();
}
};
- const goToPreviousStep = (activeStep, onBack, errorHandler) => {
+ const onBack = (goToPrevStep, errorHandler) => {
// first reset validation state to default
- setIsFormValid(true);
- onBack();
+ setIsFormValid(false);
+ goToPrevStep();
};
+ const isFirstScreen = (
+ activeStep.id === "installation-language" || (activeStep.id === "installation-method" && !isBootIso)
+ );
+
+ const nextButtonText = (
+ activeStep.id === "installation-review"
+ ? getScenario(storageScenarioId).buttonLabel
+ : _("Next")
+ );
+
+ const footerHelperText = stepsOrder.find(step => step.id === activeStep.id)?.footerHelperText;
+
return (
-
-
- {({ activeStep, onNext, onBack }) => {
- const currentStep = stepsOrder.find(s => s.id === activeStep.id);
- const footerHelperText = currentStep?.footerHelperText;
- const isFirstScreen = stepsOrder.filter(step => !step.isHidden)[0].id === activeStep.id;
- const nextButtonText = currentStep?.nextButtonText || _("Next");
- const nextButtonVariant = currentStep?.nextButtonVariant || "primary";
-
- return (
-
- {activeStep.id === "installation-review" &&
- nextWaitsConfirmation &&
- { setShowWizard(false); cockpit.location.go(["installation-progress"]) }}
- setNextWaitsConfirmation={setNextWaitsConfirmation}
- storageScenarioId={storageScenarioId}
- />}
- {quitWaitsConfirmation &&
- }
- {footerHelperText}
-
-
-
-
-
-
- );
- }}
-
-
+
+
+ {activeStep.id === "installation-review" &&
+ nextWaitsConfirmation &&
+ { setShowWizard(false); cockpit.location.go(["installation-progress"]) }}
+ setNextWaitsConfirmation={setNextWaitsConfirmation}
+ storageScenarioId={storageScenarioId}
+ />}
+ {quitWaitsConfirmation &&
+ }
+ {footerHelperText}
+
+
+
+
+
+
+
);
};
diff --git a/src/components/review/ReviewConfiguration.jsx b/src/components/review/ReviewConfiguration.jsx
index a597759717..3080efda0c 100644
--- a/src/components/review/ReviewConfiguration.jsx
+++ b/src/components/review/ReviewConfiguration.jsx
@@ -187,7 +187,7 @@ export const ReviewConfiguration = ({ deviceData, diskSelection, language, local
);
};
-export const ReviewConfigurationConfirmModal = ({ idPrefix, onNext, setNextWaitsConfirmation, storageScenarioId }) => {
+export const ReviewConfigurationConfirmModal = ({ idPrefix, goToNextStep, setNextWaitsConfirmation, storageScenarioId }) => {
const scenario = getScenario(storageScenarioId);
return (
{
setNextWaitsConfirmation(false);
- onNext();
+ goToNextStep();
}}
variant={scenario.buttonVariant}
>
diff --git a/src/components/users/Accounts.jsx b/src/components/users/Accounts.jsx
index 8ab65260ae..bce838273b 100644
--- a/src/components/users/Accounts.jsx
+++ b/src/components/users/Accounts.jsx
@@ -17,6 +17,8 @@
import cockpit from "cockpit";
import React, { useState, useEffect } from "react";
+import * as python from "python.js";
+import encryptUserPw from "../../scripts/encrypt-user-pw.py";
import {
Form,
@@ -45,12 +47,17 @@ export function getAccountsState (
};
}
+export const cryptUserPassword = async (password) => {
+ const crypted = await python.spawn(encryptUserPw, password, { err: "message", environ: ["LC_ALL=C.UTF-8"] });
+ return crypted;
+};
+
export const accountsToDbusUsers = (accounts) => {
return [{
name: cockpit.variant("s", accounts.userAccount || ""),
gecos: cockpit.variant("s", accounts.fullName || ""),
password: cockpit.variant("s", accounts.password || ""),
- "is-crypted": cockpit.variant("b", false),
+ "is-crypted": cockpit.variant("b", true),
groups: cockpit.variant("as", ["wheel"]),
}];
};
diff --git a/src/scripts/encrypt-user-pw.py b/src/scripts/encrypt-user-pw.py
new file mode 100644
index 0000000000..91ee9af413
--- /dev/null
+++ b/src/scripts/encrypt-user-pw.py
@@ -0,0 +1,42 @@
+import crypt
+from random import SystemRandom as sr
+import sys
+
+# Using the function from pyanaconda/core/users.py
+
+def crypt_password(password):
+ """Crypt a password.
+
+ Process a password with appropriate salted one-way algorithm.
+
+ :param str password: password to be crypted
+ :returns: crypted representation of the original password
+ :rtype: str
+ """
+ # yescrypt is not supported by Python's crypt module,
+ # so we need to generate the setting ourselves
+ b64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ setting = "$y$j9T$" + "".join(sr().choice(b64) for _sc in range(24))
+
+ # and try to compute the password hash using our yescrypt setting
+ try:
+ cryptpw = crypt.crypt(password, setting)
+
+ # Fallback to sha512crypt, if yescrypt is not supported
+ except OSError:
+ sys.stderr.write("yescrypt is not supported, falling back to sha512crypt\n")
+ try:
+ cryptpw = crypt.crypt(password, crypt.METHOD_SHA512)
+ except OSError as exc:
+ raise RuntimeError(
+ "Unable to encrypt password: unsupported algorithm {}".format(crypt.METHOD_SHA512)
+ ) from exc
+
+ return cryptpw
+
+
+try:
+ print(crypt_password(sys.argv[1]), end="")
+except Exception as e:
+ sys.stderr.write(str(e) + "\n")
+ sys.exit(1)
diff --git a/test/check-users b/test/check-users
index 2d44d7f978..59fe08e848 100755
--- a/test/check-users
+++ b/test/check-users
@@ -44,8 +44,7 @@ class TestUsers(anacondalib.VirtInstallMachineCase):
users = u.dbus_get_users()
self.assertIn('"groups" as 1 "wheel"', users)
- self.assertIn('"is-crypted" b false', users)
- self.assertIn('"password" s "password"', users)
+ self.assertIn('"is-crypted" b true', users)
if __name__ == '__main__':
test_main()