Téléverser les fichiers vers "workstation-setup/src"

This commit is contained in:
2026-08-20 10:37:37 +02:00
parent 9ba9b86b18
commit 96e2424be2
4 changed files with 181 additions and 32 deletions
+100 -27
View File
@@ -15,14 +15,29 @@ Kirigami.ApplicationWindow {
property string operationError: ""
property bool closeAttempted: false
property var steps: [
{ "title": "Bienvenue", "subtitle": "Première connexion" },
{ "title": "Mot de passe", "subtitle": "Protection des données" },
{ "title": "YubiKey", "subtitle": "Création du code PIN" },
{ "title": "Association", "subtitle": "Association de la clé" },
{ "title": "Terminé", "subtitle": "Poste prêt" }
property var stepsWithYubiKey: [
{ "page": 0, "title": "Bienvenue", "subtitle": "Première connexion" },
{ "page": 1, "title": "Mot de passe", "subtitle": "Protection des données" },
{ "page": 2, "title": "YubiKey", "subtitle": "Création du code PIN" },
{ "page": 3, "title": "Association", "subtitle": "Association de la clé" },
{ "page": 4, "title": "Terminé", "subtitle": "Poste prêt" }
]
property var stepsPasswordOnly: [
{ "page": 0, "title": "Bienvenue", "subtitle": "Première connexion" },
{ "page": 1, "title": "Mot de passe", "subtitle": "Protection des données" },
{ "page": 4, "title": "Terminé", "subtitle": "Poste prêt" }
]
property var steps: provisioning.yubiKeyRequested ? stepsWithYubiKey : stepsPasswordOnly
property int visibleCurrentStep: {
for (var i = 0; i < steps.length; ++i) {
if (steps[i].page === currentStep)
return i
}
return 0
}
property bool passwordStepValid:
currentPassword.text.length > 0 &&
newPassword.text.length >= 8 &&
@@ -47,8 +62,10 @@ Kirigami.ApplicationWindow {
currentStep = 4
else if (provisioning.pinDone)
currentStep = 3
else if (provisioning.passwordDone)
currentStep = 2
else if (provisioning.passwordDone && provisioning.yubiKeyChoiceMade)
currentStep = provisioning.yubiKeyRequested ? 2 : 4
else if (provisioning.yubiKeyChoiceMade)
currentStep = 1
else
currentStep = 0
}
@@ -56,10 +73,16 @@ Kirigami.ApplicationWindow {
Connections {
target: provisioning
function onYubiKeyChoiceFinished(success, message) {
root.operationError = success ? "" : message
if (success)
root.currentStep = 1
}
function onPasswordChangeFinished(success, message) {
root.operationError = success ? "" : message
if (success)
root.currentStep = 2
root.currentStep = provisioning.yubiKeyRequested ? 2 : 4
}
function onPinChangeFinished(success, message) {
@@ -150,6 +173,32 @@ Kirigami.ApplicationWindow {
}
}
component SecondaryButton: Controls.Button {
id: control
implicitHeight: 52
leftPadding: 26
rightPadding: 26
implicitWidth: Math.max(210, buttonLabel.implicitWidth + leftPadding + rightPadding)
contentItem: Controls.Label {
id: buttonLabel
text: control.text
color: control.enabled ? "#1d4ed8" : "#64748b"
font.pixelSize: 15
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.NoWrap
}
background: Rectangle {
radius: 11
color: control.down ? "#dbeafe" : control.hovered ? "#eff6ff" : "#ffffff"
border.width: 1
border.color: control.enabled ? "#93c5fd" : "#cbd5e1"
}
}
component SecretField: Controls.TextField {
id: field
property bool secretVisible: false
@@ -319,7 +368,7 @@ Kirigami.ApplicationWindow {
Layout.fillWidth: true
implicitHeight: 66
radius: 12
color: index === root.currentStep ? "#173f6f" : "transparent"
color: index === root.visibleCurrentStep ? "#173f6f" : "transparent"
RowLayout {
anchors.fill: parent
@@ -330,13 +379,13 @@ Kirigami.ApplicationWindow {
Layout.preferredWidth: 34
Layout.preferredHeight: 34
radius: 17
color: index < root.currentStep ? "#22c55e"
: index === root.currentStep ? "#ffffff"
color: index < root.visibleCurrentStep ? "#22c55e"
: index === root.visibleCurrentStep ? "#ffffff"
: "#28517e"
Controls.Label {
anchors.centerIn: parent
text: index < root.currentStep ? "✓" : (index + 1)
color: index === root.currentStep ? "#0f2b50" : "white"
text: index < root.visibleCurrentStep ? "✓" : (index + 1)
color: index === root.visibleCurrentStep ? "#0f2b50" : "white"
font.weight: Font.Bold
}
}
@@ -356,7 +405,7 @@ Kirigami.ApplicationWindow {
text: modelData.title
color: "white"
font.pixelSize: 14
font.weight: index === root.currentStep ? Font.DemiBold : Font.Medium
font.weight: index === root.visibleCurrentStep ? Font.DemiBold : Font.Medium
horizontalAlignment: Text.AlignLeft
}
@@ -407,7 +456,7 @@ Kirigami.ApplicationWindow {
Layout.fillWidth: true
implicitHeight: 5
radius: 3
color: index <= root.currentStep ? "#2563eb" : "#e2e8f0"
color: index <= root.visibleCurrentStep ? "#2563eb" : "#e2e8f0"
}
}
}
@@ -450,7 +499,7 @@ Kirigami.ApplicationWindow {
Controls.Label {
Layout.maximumWidth: 720
Layout.alignment: Qt.AlignHCenter
text: "Votre espace personnel est déjà chiffré. Il reste à remplacer le mot de passe temporaire et à préparer votre YubiKey."
text: "Votre espace personnel est déjà chiffré. Vous allez maintenant personnaliser votre mot de passe."
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: "#64748b"
@@ -460,16 +509,34 @@ Kirigami.ApplicationWindow {
InfoBox {
Layout.maximumWidth: 720
Layout.alignment: Qt.AlignHCenter
iconText: "🔐"
text: "À la fin, vous pourrez ouvrir votre espace personnel soit avec votre mot de passe, soit avec votre YubiKey et son code PIN."
iconText: "?"
text: "Disposez-vous d'une YubiKey ? Si oui, l'assistant vous aidera ensuite à la configurer. Si non, seule la personnalisation de votre mot de passe sera nécessaire."
}
ErrorBox {
Layout.maximumWidth: 720
Layout.alignment: Qt.AlignHCenter
}
Item { Layout.fillHeight: true }
PrimaryButton {
RowLayout {
Layout.alignment: Qt.AlignHCenter
text: "Commencer"
onClicked: {
root.operationError = ""
root.currentStep = 1
spacing: 14
SecondaryButton {
text: "Non, je n'en ai pas"
enabled: !provisioning.busy
onClicked: {
root.operationError = ""
provisioning.setYubiKeyUsage(false)
}
}
PrimaryButton {
text: "Oui, j'ai une YubiKey"
enabled: !provisioning.busy
onClicked: {
root.operationError = ""
provisioning.setYubiKeyUsage(true)
}
}
}
}
@@ -491,7 +558,9 @@ Kirigami.ApplicationWindow {
}
Controls.Label {
Layout.fillWidth: true
text: "Ce mot de passe reste votre solution de secours si la YubiKey est oubliée ou indisponible."
text: provisioning.yubiKeyRequested
? "Ce mot de passe reste votre solution de secours si la YubiKey est oubliée ou indisponible."
: "Ce mot de passe vous permettra d'ouvrir votre espace personnel chiffré."
wrapMode: Text.WordWrap
color: "#64748b"
font.pixelSize: 14
@@ -771,7 +840,9 @@ Kirigami.ApplicationWindow {
Controls.Label {
Layout.maximumWidth: 720
Layout.alignment: Qt.AlignHCenter
text: "Votre espace personnel est chiffré et votre YubiKey est prête. Vous pourrez ouvrir votre session avec votre mot de passe ou avec votre YubiKey et son code PIN."
text: provisioning.yubiKeyRequested
? "Votre espace personnel est chiffré et votre YubiKey est prête. Vous pourrez ouvrir votre session avec votre mot de passe ou avec votre YubiKey et son code PIN."
: "Votre espace personnel est chiffré et votre mot de passe a été personnalisé. Vous pourrez ouvrir votre session avec ce mot de passe."
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: "#64748b"
@@ -783,7 +854,9 @@ Kirigami.ApplicationWindow {
Layout.maximumWidth: 720
Layout.alignment: Qt.AlignHCenter
iconText: "✓"
text: "Votre configuration sécurisée est terminée. Conservez votre YubiKey avec vous et mémorisez son code PIN."
text: provisioning.yubiKeyRequested
? "Votre configuration sécurisée est terminée. Conservez votre YubiKey avec vous et mémorisez son code PIN."
: "Votre configuration sécurisée est terminée."
}
Item { Layout.fillHeight: true }
+67 -3
View File
@@ -42,13 +42,15 @@ ProvisioningBackend::ProvisioningBackend(QString targetUser, QObject *parent)
, m_targetUser(std::move(targetUser))
{
refreshState();
appendLog(QStringLiteral("START currentUser=%1 targetUser=%2 authorized=%3 passwordDone=%4 pinDone=%5 fidoDone=%6")
appendLog(QStringLiteral("START currentUser=%1 targetUser=%2 authorized=%3 passwordDone=%4 pinDone=%5 fidoDone=%6 yubiKeyChoice=%7 yubiKeyRequested=%8")
.arg(m_currentUser,
m_targetUser,
authorizedUser() ? QStringLiteral("yes") : QStringLiteral("no"),
m_passwordDone ? QStringLiteral("yes") : QStringLiteral("no"),
m_pinDone ? QStringLiteral("yes") : QStringLiteral("no"),
m_fidoDone ? QStringLiteral("yes") : QStringLiteral("no")));
m_fidoDone ? QStringLiteral("yes") : QStringLiteral("no"),
yubiKeyChoiceMade() ? QStringLiteral("yes") : QStringLiteral("no"),
yubiKeyRequested() ? QStringLiteral("yes") : QStringLiteral("no")));
}
ProvisioningBackend::~ProvisioningBackend()
@@ -114,17 +116,35 @@ QString ProvisioningBackend::fidoMarker() const
return stateDirectory() + QStringLiteral("/yubikey-fido-enrolled");
}
QString ProvisioningBackend::yubiKeyEnabledMarker() const
{
return stateDirectory() + QStringLiteral("/yubikey-enabled");
}
QString ProvisioningBackend::yubiKeySkippedMarker() const
{
return stateDirectory() + QStringLiteral("/yubikey-skipped");
}
void ProvisioningBackend::refreshState()
{
const bool oldPassword = m_passwordDone;
const bool oldPin = m_pinDone;
const bool oldFido = m_fidoDone;
const bool oldYubiKeyEnabled = m_yubiKeyEnabled;
const bool oldYubiKeySkipped = m_yubiKeySkipped;
m_passwordDone = QFileInfo::exists(passwordMarker());
m_pinDone = QFileInfo::exists(pinMarker());
m_fidoDone = QFileInfo::exists(fidoMarker());
m_yubiKeyEnabled = QFileInfo::exists(yubiKeyEnabledMarker());
m_yubiKeySkipped = QFileInfo::exists(yubiKeySkippedMarker());
if (oldPassword != m_passwordDone || oldPin != m_pinDone || oldFido != m_fidoDone) {
if (oldPassword != m_passwordDone
|| oldPin != m_pinDone
|| oldFido != m_fidoDone
|| oldYubiKeyEnabled != m_yubiKeyEnabled
|| oldYubiKeySkipped != m_yubiKeySkipped) {
emit stateChanged();
}
}
@@ -321,6 +341,38 @@ void ProvisioningBackend::emitResult(Operation operation, bool success, const QS
}
}
void ProvisioningBackend::setYubiKeyUsage(bool enabled)
{
if (!authorizedUser()) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
return;
}
if (m_busy) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
// Un compte déjà passé par les étapes YubiKey est considéré comme utilisant
// toujours sa clé. Cela assure la compatibilité avec les états v1.9.0.
if (!enabled && (m_pinDone || m_fidoDone)) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Une YubiKey est déjà configurée pour ce compte."));
return;
}
const QString selectedMarker = enabled ? yubiKeyEnabledMarker() : yubiKeySkippedMarker();
const QString obsoleteMarker = enabled ? yubiKeySkippedMarker() : yubiKeyEnabledMarker();
if (!createMarker(selectedMarker)) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Impossible d'enregistrer votre choix."));
return;
}
QFile::remove(obsoleteMarker);
refreshState();
appendLog(QStringLiteral("YUBIKEY choice=%1").arg(enabled ? QStringLiteral("enabled") : QStringLiteral("skipped")));
emit yubiKeyChoiceFinished(true, QString());
}
void ProvisioningBackend::changePassword(const QString &currentPassword,
const QString &newPassword,
const QString &confirmation)
@@ -333,6 +385,10 @@ void ProvisioningBackend::changePassword(const QString &currentPassword,
emit passwordChangeFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
if (!yubiKeyChoiceMade()) {
emit passwordChangeFinished(false, QStringLiteral("Indiquez d'abord si vous disposez d'une YubiKey."));
return;
}
if (m_passwordDone) {
emit passwordChangeFinished(true, QStringLiteral("Le mot de passe a déjà été personnalisé."));
return;
@@ -372,6 +428,10 @@ void ProvisioningBackend::initializePin(const QString &newPin,
emit pinChangeFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
if (!yubiKeyRequested()) {
emit pinChangeFinished(false, QStringLiteral("Aucune YubiKey n'a été sélectionnée pour ce compte."));
return;
}
if (!m_passwordDone) {
emit pinChangeFinished(false, QStringLiteral("Personnalisez d'abord votre mot de passe."));
return;
@@ -410,6 +470,10 @@ void ProvisioningBackend::enrollFido(const QString &currentPassword,
emit fidoEnrollmentFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
if (!yubiKeyRequested()) {
emit fidoEnrollmentFinished(false, QStringLiteral("Aucune YubiKey n'a été sélectionnée pour ce compte."));
return;
}
if (!m_passwordDone || !m_pinDone) {
emit fidoEnrollmentFinished(false, QStringLiteral("Le mot de passe et le code PIN doivent être configurés avant d'associer la YubiKey."));
return;
+11 -1
View File
@@ -13,6 +13,8 @@ class ProvisioningBackend final : public QObject
Q_PROPERTY(bool passwordDone READ passwordDone NOTIFY stateChanged)
Q_PROPERTY(bool pinDone READ pinDone NOTIFY stateChanged)
Q_PROPERTY(bool fidoDone READ fidoDone NOTIFY stateChanged)
Q_PROPERTY(bool yubiKeyChoiceMade READ yubiKeyChoiceMade NOTIFY stateChanged)
Q_PROPERTY(bool yubiKeyRequested READ yubiKeyRequested NOTIFY stateChanged)
Q_PROPERTY(bool complete READ complete NOTIFY stateChanged)
Q_PROPERTY(QString currentUser READ currentUser CONSTANT)
Q_PROPERTY(QString targetUser READ targetUser CONSTANT)
@@ -29,7 +31,9 @@ public:
bool passwordDone() const { return m_passwordDone; }
bool pinDone() const { return m_pinDone; }
bool fidoDone() const { return m_fidoDone; }
bool complete() const { return m_passwordDone && m_pinDone && m_fidoDone; }
bool yubiKeyChoiceMade() const { return m_yubiKeyEnabled || m_yubiKeySkipped || m_pinDone || m_fidoDone; }
bool yubiKeyRequested() const { return !m_yubiKeySkipped && (m_yubiKeyEnabled || m_pinDone || m_fidoDone); }
bool complete() const { return m_passwordDone && (m_yubiKeySkipped || (m_pinDone && m_fidoDone)); }
QString currentUser() const { return m_currentUser; }
QString targetUser() const { return m_targetUser; }
bool authorizedUser() const;
@@ -43,7 +47,10 @@ public:
QString passwordMarker() const;
QString pinMarker() const;
QString fidoMarker() const;
QString yubiKeyEnabledMarker() const;
QString yubiKeySkippedMarker() const;
Q_INVOKABLE void setYubiKeyUsage(bool enabled);
Q_INVOKABLE void changePassword(const QString &currentPassword,
const QString &newPassword,
const QString &confirmation);
@@ -55,6 +62,7 @@ public:
signals:
void busyChanged();
void stateChanged();
void yubiKeyChoiceFinished(bool success, const QString &message);
void passwordChangeFinished(bool success, const QString &message);
void pinChangeFinished(bool success, const QString &message);
void fidoEnrollmentFinished(bool success, const QString &message);
@@ -95,6 +103,8 @@ private:
bool m_passwordDone = false;
bool m_pinDone = false;
bool m_fidoDone = false;
bool m_yubiKeyEnabled = false;
bool m_yubiKeySkipped = false;
bool m_helperTimedOut = false;
QString m_currentUser;
QString m_targetUser;
+3 -1
View File
@@ -41,7 +41,7 @@ int main(int argc, char *argv[])
QGuiApplication::setOrganizationName(QStringLiteral("Raspot"));
QGuiApplication::setOrganizationDomain(QStringLiteral("raspot.in"));
QGuiApplication::setApplicationName(QStringLiteral("NixOS Workstations Setup"));
QGuiApplication::setApplicationVersion(QStringLiteral("1.9.0"));
QGuiApplication::setApplicationVersion(QStringLiteral("1.9.1"));
QGuiApplication::setDesktopFileName(QStringLiteral("org.raspot.nixosworkstations.setup"));
QCommandLineParser parser;
@@ -90,6 +90,8 @@ int main(int argc, char *argv[])
qInfo().noquote() << "password_done=" + (provisioning.passwordDone() ? QStringLiteral("yes") : QStringLiteral("no"));
qInfo().noquote() << "pin_marker=" + provisioning.pinMarker();
qInfo().noquote() << "pin_done=" + (provisioning.pinDone() ? QStringLiteral("yes") : QStringLiteral("no"));
qInfo().noquote() << "yubikey_choice=" + (provisioning.yubiKeyChoiceMade() ? QStringLiteral("yes") : QStringLiteral("no"));
qInfo().noquote() << "yubikey_requested=" + (provisioning.yubiKeyRequested() ? QStringLiteral("yes") : QStringLiteral("no"));
qInfo().noquote() << "log=" + provisioning.diagnosticLogPath();
return provisioning.authorizedUser() ? 0 : 3;
}