From 96e2424be210cde19dc73648991c2fd976ee5a7e Mon Sep 17 00:00:00 2001 From: Olivier <1+olivier@noreply.raspot.in> Date: Thu, 20 Aug 2026 10:37:37 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"workstation-setup/src"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- workstation-setup/src/Main.qml | 127 ++++++++++++++---- workstation-setup/src/ProvisioningBackend.cpp | 70 +++++++++- workstation-setup/src/ProvisioningBackend.h | 12 +- workstation-setup/src/main.cpp | 4 +- 4 files changed, 181 insertions(+), 32 deletions(-) diff --git a/workstation-setup/src/Main.qml b/workstation-setup/src/Main.qml index b557877..3b42cb1 100644 --- a/workstation-setup/src/Main.qml +++ b/workstation-setup/src/Main.qml @@ -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 } diff --git a/workstation-setup/src/ProvisioningBackend.cpp b/workstation-setup/src/ProvisioningBackend.cpp index d2db654..8c7ac07 100644 --- a/workstation-setup/src/ProvisioningBackend.cpp +++ b/workstation-setup/src/ProvisioningBackend.cpp @@ -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 ¤tPassword, const QString &newPassword, const QString &confirmation) @@ -333,6 +385,10 @@ void ProvisioningBackend::changePassword(const QString ¤tPassword, 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 ¤tPassword, 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; diff --git a/workstation-setup/src/ProvisioningBackend.h b/workstation-setup/src/ProvisioningBackend.h index b55e08e..3da6b0d 100644 --- a/workstation-setup/src/ProvisioningBackend.h +++ b/workstation-setup/src/ProvisioningBackend.h @@ -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 ¤tPassword, 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; diff --git a/workstation-setup/src/main.cpp b/workstation-setup/src/main.cpp index 2367e50..fb9031a 100644 --- a/workstation-setup/src/main.cpp +++ b/workstation-setup/src/main.cpp @@ -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; }