From 9e5668d0252250f51f6afeb5a959efe3a39db8c5 Mon Sep 17 00:00:00 2001 From: Olivier <1+olivier@noreply.raspot.in> Date: Wed, 19 Aug 2026 16:41:52 +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 | 108 ++++++++++++------ workstation-setup/src/ProvisioningBackend.cpp | 92 +++++++++++++++ workstation-setup/src/ProvisioningBackend.h | 13 +++ workstation-setup/src/main.cpp | 2 +- 4 files changed, 182 insertions(+), 33 deletions(-) diff --git a/workstation-setup/src/Main.qml b/workstation-setup/src/Main.qml index a4899e2..e30963a 100644 --- a/workstation-setup/src/Main.qml +++ b/workstation-setup/src/Main.qml @@ -339,26 +339,32 @@ Kirigami.ApplicationWindow { } } - ColumnLayout { + Item { Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: 2 + Layout.fillHeight: true - Controls.Label { - Layout.fillWidth: true - text: modelData.title - color: "white" - font.pixelSize: 14 - font.weight: index === root.currentStep ? Font.DemiBold : Font.Medium - horizontalAlignment: Text.AlignLeft - } + Column { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 2 - Controls.Label { - Layout.fillWidth: true - text: modelData.subtitle - color: "#b9cee5" - font.pixelSize: 11 - horizontalAlignment: Text.AlignLeft + Controls.Label { + width: parent.width + text: modelData.title + color: "white" + font.pixelSize: 14 + font.weight: index === root.currentStep ? Font.DemiBold : Font.Medium + horizontalAlignment: Text.AlignLeft + } + + Controls.Label { + width: parent.width + text: modelData.subtitle + color: "#b9cee5" + font.pixelSize: 11 + horizontalAlignment: Text.AlignLeft + } } } } @@ -608,34 +614,72 @@ Kirigami.ApplicationWindow { text: "Saisissez le mot de passe que vous venez de choisir et le PIN de la YubiKey. Aucune recovery key n'est demandée dans ce workflow." } + InfoBox { + visible: !provisioning.busy + iconText: "☝" + text: "Après avoir lancé l'association, l'assistant vous indiquera précisément quand toucher la YubiKey. Plusieurs touchers peuvent être nécessaires." + } + Rectangle { + id: fidoLiveStatus + visible: provisioning.busy && root.currentStep === 3 Layout.fillWidth: true - implicitHeight: touchInstruction.implicitHeight + 32 + implicitHeight: fidoLiveRow.implicitHeight + 32 radius: 12 - color: "#fffbeb" - border.width: 2 - border.color: "#f59e0b" + color: provisioning.fidoTouchRequired ? "#fff7ed" : "#eff6ff" + border.width: provisioning.fidoTouchRequired ? 3 : 1 + border.color: provisioning.fidoTouchRequired ? "#f97316" : "#bfdbfe" + + SequentialAnimation on opacity { + running: fidoLiveStatus.visible && provisioning.fidoTouchRequired + loops: Animation.Infinite + NumberAnimation { from: 1.0; to: 0.58; duration: 520 } + NumberAnimation { from: 0.58; to: 1.0; duration: 520 } + } RowLayout { + id: fidoLiveRow anchors.fill: parent anchors.margins: 16 spacing: 14 - Controls.Label { - text: "☝" - font.pixelSize: 25 - Layout.alignment: Qt.AlignTop + Controls.BusyIndicator { + visible: !provisioning.fidoTouchRequired + running: visible + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 } Controls.Label { - id: touchInstruction + visible: provisioning.fidoTouchRequired + text: "☝" + font.pixelSize: 30 + Layout.alignment: Qt.AlignTop + } + + ColumnLayout { Layout.fillWidth: true - text: "IMPORTANT : après avoir cliqué sur « Associer la YubiKey », surveillez la clé. Elle peut clignoter plusieurs fois. Touchez-la à CHAQUE clignotement jusqu'à la fin de l'association." - wrapMode: Text.WordWrap - color: "#92400e" - font.pixelSize: 14 - font.weight: Font.DemiBold - lineHeight: 1.25 + spacing: 4 + + Controls.Label { + Layout.fillWidth: true + text: provisioning.fidoTouchRequired + ? "TOUCHEZ LA YUBIKEY MAINTENANT" + : "Association FIDO2 en cours" + wrapMode: Text.WordWrap + color: provisioning.fidoTouchRequired ? "#9a3412" : "#1d4ed8" + font.pixelSize: provisioning.fidoTouchRequired ? 18 : 15 + font.weight: Font.Bold + } + + Controls.Label { + Layout.fillWidth: true + text: provisioning.fidoStatus + wrapMode: Text.WordWrap + color: provisioning.fidoTouchRequired ? "#9a3412" : "#334155" + font.pixelSize: 13 + lineHeight: 1.2 + } } } } diff --git a/workstation-setup/src/ProvisioningBackend.cpp b/workstation-setup/src/ProvisioningBackend.cpp index 79bb267..b88d049 100644 --- a/workstation-setup/src/ProvisioningBackend.cpp +++ b/workstation-setup/src/ProvisioningBackend.cpp @@ -213,6 +213,70 @@ void ProvisioningBackend::setBusy(bool busy) emit busyChanged(); } + +void ProvisioningBackend::resetFidoInteraction() +{ + const bool changed = m_fidoTouchRequired + || m_fidoTouchRequestCount != 0 + || !m_fidoStatus.isEmpty(); + + m_fidoTouchRequired = false; + m_fidoTouchRequestCount = 0; + m_fidoStatus.clear(); + m_helperStdoutBuffer.clear(); + + if (changed) { + emit fidoInteractionChanged(); + } +} + +void ProvisioningBackend::handleHelperStdout(Operation operation, const QByteArray &data) +{ + if (operation != Operation::Fido || data.isEmpty()) { + return; + } + + m_helperStdoutBuffer += data; + + qsizetype newlineIndex = -1; + while ((newlineIndex = m_helperStdoutBuffer.indexOf('\n')) >= 0) { + QByteArray line = m_helperStdoutBuffer.left(newlineIndex); + m_helperStdoutBuffer.remove(0, newlineIndex + 1); + if (!line.isEmpty() && line.endsWith('\r')) { + line.chop(1); + } + + bool changed = false; + if (line == QByteArrayLiteral("EVENT_FIDO_INITIALIZING")) { + m_fidoTouchRequired = false; + m_fidoStatus = QStringLiteral("Initialisation de l'association FIDO2…"); + changed = true; + } else if (line == QByteArrayLiteral("EVENT_FIDO_GENERATING")) { + m_fidoTouchRequired = false; + m_fidoStatus = QStringLiteral("Génération du secret FIDO2…"); + changed = true; + } else if (line == QByteArrayLiteral("EVENT_FIDO_UPDATING_HOME")) { + m_fidoTouchRequired = false; + m_fidoStatus = QStringLiteral("Mise à jour du home chiffré…"); + changed = true; + } else if (line == QByteArrayLiteral("EVENT_FIDO_SYNCHRONIZING")) { + m_fidoTouchRequired = false; + m_fidoStatus = QStringLiteral("Synchronisation des clés de chiffrement…"); + changed = true; + } else if (line == QByteArrayLiteral("EVENT_FIDO_TOUCH_REQUIRED")) { + m_fidoTouchRequired = true; + ++m_fidoTouchRequestCount; + m_fidoStatus = QStringLiteral("Touchez la YubiKey maintenant — demande %1. Il peut y avoir plusieurs touchers pendant cette étape.") + .arg(m_fidoTouchRequestCount); + changed = true; + } + + if (changed) { + emit fidoInteractionChanged(); + } + } +} + void ProvisioningBackend::secureClear(QByteArray &data) { if (!data.isEmpty()) { @@ -359,6 +423,10 @@ void ProvisioningBackend::enrollFido(const QString ¤tPassword, return; } + resetFidoInteraction(); + m_fidoStatus = QStringLiteral("Préparation de l'association FIDO2…"); + emit fidoInteractionChanged(); + startHelper(Operation::Fido, QString::fromUtf8(NIXOS_WORKSTATIONS_FIDO_HELPER_PATH), currentPassword, @@ -396,6 +464,7 @@ void ProvisioningBackend::startHelper(Operation operation, secureClear(third); m_helperTimedOut = false; + m_helperStdoutBuffer.clear(); setBusy(true); auto *process = new QProcess(this); @@ -432,6 +501,14 @@ void ProvisioningBackend::startHelper(Operation operation, timer->start(); }); + connect(process, &QProcess::readyReadStandardOutput, this, + [this, process, operation]() { + if (m_process != process) { + return; + } + handleHelperStdout(operation, process->readAllStandardOutput()); + }); + connect(process, &QProcess::errorOccurred, this, [this, process, timer, operation](QProcess::ProcessError error) { if (m_process != process || error != QProcess::FailedToStart) { @@ -443,6 +520,9 @@ void ProvisioningBackend::startHelper(Operation operation, process->deleteLater(); setBusy(false); appendLog(operationName(operation) + QStringLiteral(" failed to start")); + if (operation == Operation::Fido) { + resetFidoInteraction(); + } emitResult(operation, false, QStringLiteral("Impossible de démarrer le composant de configuration.")); }); @@ -459,6 +539,10 @@ void ProvisioningBackend::startHelper(Operation operation, QByteArray stderrData = process->readAllStandardError(); QByteArray stdoutData = process->readAllStandardOutput(); + if (operation == Operation::Fido && !stdoutData.isEmpty()) { + handleHelperStdout(operation, stdoutData); + stdoutData.clear(); + } QByteArray combined = stderrData + '\n' + stdoutData; const bool timedOut = m_helperTimedOut; @@ -515,6 +599,14 @@ void ProvisioningBackend::startHelper(Operation operation, setBusy(false); refreshState(); + if (operation == Operation::Fido) { + m_fidoTouchRequired = false; + m_fidoStatus = helperSucceeded && markerCreated + ? QStringLiteral("Association FIDO2 terminée.") + : QStringLiteral("Association FIDO2 interrompue."); + emit fidoInteractionChanged(); + } + emitResult(operation, helperSucceeded && markerCreated, message); }); diff --git a/workstation-setup/src/ProvisioningBackend.h b/workstation-setup/src/ProvisioningBackend.h index a07a953..b55e08e 100644 --- a/workstation-setup/src/ProvisioningBackend.h +++ b/workstation-setup/src/ProvisioningBackend.h @@ -17,6 +17,9 @@ class ProvisioningBackend final : public QObject Q_PROPERTY(QString currentUser READ currentUser CONSTANT) Q_PROPERTY(QString targetUser READ targetUser CONSTANT) Q_PROPERTY(bool authorizedUser READ authorizedUser CONSTANT) + Q_PROPERTY(bool fidoTouchRequired READ fidoTouchRequired NOTIFY fidoInteractionChanged) + Q_PROPERTY(int fidoTouchRequestCount READ fidoTouchRequestCount NOTIFY fidoInteractionChanged) + Q_PROPERTY(QString fidoStatus READ fidoStatus NOTIFY fidoInteractionChanged) public: explicit ProvisioningBackend(QString targetUser, QObject *parent = nullptr); @@ -30,6 +33,9 @@ public: QString currentUser() const { return m_currentUser; } QString targetUser() const { return m_targetUser; } bool authorizedUser() const; + bool fidoTouchRequired() const { return m_fidoTouchRequired; } + int fidoTouchRequestCount() const { return m_fidoTouchRequestCount; } + QString fidoStatus() const { return m_fidoStatus; } // Getters de diagnostic utilisés par main.cpp (--diagnose). QString stateDirectory() const; @@ -52,6 +58,7 @@ signals: void passwordChangeFinished(bool success, const QString &message); void pinChangeFinished(bool success, const QString &message); void fidoEnrollmentFinished(bool success, const QString &message); + void fidoInteractionChanged(); private: enum class Operation { @@ -69,6 +76,8 @@ private: bool createMarker(const QString &path); void appendLog(const QString &message) const; void setBusy(bool busy); + void resetFidoInteraction(); + void handleHelperStdout(Operation operation, const QByteArray &data); void startHelper(Operation operation, const QString &helperPath, @@ -91,4 +100,8 @@ private: QString m_targetUser; QProcess *m_process = nullptr; QByteArray m_pendingPayload; + QByteArray m_helperStdoutBuffer; + bool m_fidoTouchRequired = false; + int m_fidoTouchRequestCount = 0; + QString m_fidoStatus; }; diff --git a/workstation-setup/src/main.cpp b/workstation-setup/src/main.cpp index bf5e851..73ef7ca 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.8.4")); + QGuiApplication::setApplicationVersion(QStringLiteral("1.8.5")); QGuiApplication::setDesktopFileName(QStringLiteral("org.raspot.nixosworkstations.setup")); QCommandLineParser parser;