Téléverser les fichiers vers "workstation-setup/src"
This commit is contained in:
@@ -1,29 +1,19 @@
|
|||||||
#include "ProvisioningBackend.h"
|
#include "ProvisioningBackend.h"
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
#include <QSaveFile>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#ifndef PASSWORD_HELPER_PATH
|
#include <utility>
|
||||||
#error PASSWORD_HELPER_PATH must be defined
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef PIN_HELPER_PATH
|
|
||||||
#error PIN_HELPER_PATH must be defined
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef TARGET_USER
|
|
||||||
#error TARGET_USER must be defined
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int kHelperTimeoutMs = 60000;
|
constexpr int kHelperTimeoutMs = 60000;
|
||||||
@@ -32,15 +22,38 @@ bool containsLineBreak(const QString &value)
|
|||||||
{
|
{
|
||||||
return value.contains(QLatin1Char('\n')) || value.contains(QLatin1Char('\r'));
|
return value.contains(QLatin1Char('\n')) || value.contains(QLatin1Char('\r'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString homeDirectoryForEffectiveUser()
|
||||||
|
{
|
||||||
|
if (passwd *entry = getpwuid(geteuid())) {
|
||||||
|
return QString::fromLocal8Bit(entry->pw_dir);
|
||||||
|
}
|
||||||
|
return QDir::homePath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProvisioningBackend::ProvisioningBackend(QObject *parent)
|
ProvisioningBackend::ProvisioningBackend(QString targetUser, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_currentUser(effectiveUserName())
|
, m_currentUser(effectiveUserName())
|
||||||
, m_targetUser(QStringLiteral(TARGET_USER))
|
, m_targetUser(std::move(targetUser))
|
||||||
, m_authorizedUser(m_currentUser == m_targetUser)
|
, m_authorizedUser(!m_targetUser.isEmpty() && m_currentUser == m_targetUser)
|
||||||
{
|
{
|
||||||
refreshState();
|
refreshState();
|
||||||
|
appendLog(QStringLiteral("START currentUser=%1 targetUser=%2 authorized=%3 passwordDone=%4 pinDone=%5")
|
||||||
|
.arg(m_currentUser,
|
||||||
|
m_targetUser,
|
||||||
|
m_authorizedUser ? QStringLiteral("yes") : QStringLiteral("no"),
|
||||||
|
m_passwordDone ? QStringLiteral("yes") : QStringLiteral("no"),
|
||||||
|
m_pinDone ? QStringLiteral("yes") : QStringLiteral("no")));
|
||||||
|
}
|
||||||
|
|
||||||
|
ProvisioningBackend::~ProvisioningBackend()
|
||||||
|
{
|
||||||
|
secureClear(m_pendingPayload);
|
||||||
|
if (m_process && m_process->state() != QProcess::NotRunning) {
|
||||||
|
m_process->kill();
|
||||||
|
m_process->waitForFinished(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ProvisioningBackend::effectiveUserName()
|
QString ProvisioningBackend::effectiveUserName()
|
||||||
@@ -56,16 +69,19 @@ QString ProvisioningBackend::stateDirectory() const
|
|||||||
{
|
{
|
||||||
const QByteArray xdgState = qgetenv("XDG_STATE_HOME");
|
const QByteArray xdgState = qgetenv("XDG_STATE_HOME");
|
||||||
if (!xdgState.isEmpty()) {
|
if (!xdgState.isEmpty()) {
|
||||||
return QString::fromLocal8Bit(xdgState)
|
const QString candidate = QString::fromLocal8Bit(xdgState);
|
||||||
+ QStringLiteral("/nixos-workstations");
|
if (QDir::isAbsolutePath(candidate)) {
|
||||||
|
return QDir::cleanPath(candidate + QStringLiteral("/nixos-workstations"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwd *entry = getpwuid(geteuid())) {
|
return QDir::cleanPath(homeDirectoryForEffectiveUser()
|
||||||
return QString::fromLocal8Bit(entry->pw_dir)
|
+ QStringLiteral("/.local/state/nixos-workstations"));
|
||||||
+ QStringLiteral("/.local/state/nixos-workstations");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QDir::homePath() + QStringLiteral("/.local/state/nixos-workstations");
|
QString ProvisioningBackend::diagnosticLogPath() const
|
||||||
|
{
|
||||||
|
return stateDirectory() + QStringLiteral("/setup.log");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ProvisioningBackend::passwordMarker() const
|
QString ProvisioningBackend::passwordMarker() const
|
||||||
@@ -91,7 +107,6 @@ void ProvisioningBackend::refreshState()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool ProvisioningBackend::ensureStateWritable()
|
bool ProvisioningBackend::ensureStateWritable()
|
||||||
{
|
{
|
||||||
const QString dir = stateDirectory();
|
const QString dir = stateDirectory();
|
||||||
@@ -99,26 +114,31 @@ bool ProvisioningBackend::ensureStateWritable()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QFile::setPermissions(dir,
|
||||||
|
QFileDevice::ReadOwner
|
||||||
|
| QFileDevice::WriteOwner
|
||||||
|
| QFileDevice::ExeOwner);
|
||||||
|
|
||||||
const QString probePath = dir + QStringLiteral("/.write-test");
|
const QString probePath = dir + QStringLiteral("/.write-test");
|
||||||
QFile probe(probePath);
|
QFile probe(probePath);
|
||||||
if (!probe.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
if (!probe.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
probe.write("ok\n");
|
|
||||||
|
const bool writeOk = probe.write("ok\n") == 3;
|
||||||
probe.close();
|
probe.close();
|
||||||
probe.remove();
|
probe.remove();
|
||||||
return true;
|
return writeOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProvisioningBackend::createMarker(const QString &path)
|
bool ProvisioningBackend::createMarker(const QString &path)
|
||||||
{
|
{
|
||||||
const QFileInfo info(path);
|
if (!ensureStateWritable()) {
|
||||||
if (!QDir().mkpath(info.absolutePath())) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile marker(path);
|
QSaveFile marker(path);
|
||||||
if (!marker.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
if (!marker.open(QIODevice::WriteOnly)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,13 +148,47 @@ bool ProvisioningBackend::createMarker(const QString &path)
|
|||||||
+ '\n';
|
+ '\n';
|
||||||
|
|
||||||
if (marker.write(content) != content.size()) {
|
if (marker.write(content) != content.size()) {
|
||||||
marker.close();
|
marker.cancelWriting();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
marker.close();
|
if (!marker.commit()) {
|
||||||
marker.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
return false;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
return QFile::setPermissions(path,
|
||||||
|
QFileDevice::ReadOwner
|
||||||
|
| QFileDevice::WriteOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProvisioningBackend::appendLog(const QString &message) const
|
||||||
|
{
|
||||||
|
const QString dir = stateDirectory();
|
||||||
|
if (!QDir().mkpath(dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile::setPermissions(dir,
|
||||||
|
QFileDevice::ReadOwner
|
||||||
|
| QFileDevice::WriteOwner
|
||||||
|
| QFileDevice::ExeOwner);
|
||||||
|
|
||||||
|
QFile log(diagnosticLogPath());
|
||||||
|
if (!log.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray line =
|
||||||
|
QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs).toUtf8()
|
||||||
|
+ QByteArrayLiteral(" ")
|
||||||
|
+ message.toUtf8()
|
||||||
|
+ '\n';
|
||||||
|
|
||||||
|
log.write(line);
|
||||||
|
log.close();
|
||||||
|
QFile::setPermissions(diagnosticLogPath(),
|
||||||
|
QFileDevice::ReadOwner
|
||||||
|
| QFileDevice::WriteOwner);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProvisioningBackend::setBusy(bool busy)
|
void ProvisioningBackend::setBusy(bool busy)
|
||||||
@@ -148,11 +202,21 @@ void ProvisioningBackend::setBusy(bool busy)
|
|||||||
|
|
||||||
void ProvisioningBackend::secureClear(QByteArray &data)
|
void ProvisioningBackend::secureClear(QByteArray &data)
|
||||||
{
|
{
|
||||||
|
if (!data.isEmpty()) {
|
||||||
volatile char *p = data.data();
|
volatile char *p = data.data();
|
||||||
for (qsizetype i = 0; i < data.size(); ++i) {
|
for (qsizetype i = 0; i < data.size(); ++i) {
|
||||||
p[i] = 0;
|
p[i] = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
data.clear();
|
data.clear();
|
||||||
|
data.squeeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProvisioningBackend::operationName(Operation operation)
|
||||||
|
{
|
||||||
|
return operation == Operation::Password
|
||||||
|
? QStringLiteral("PASSWORD")
|
||||||
|
: QStringLiteral("PIN");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProvisioningBackend::changePassword(const QString ¤tPassword,
|
void ProvisioningBackend::changePassword(const QString ¤tPassword,
|
||||||
@@ -160,12 +224,15 @@ void ProvisioningBackend::changePassword(const QString ¤tPassword,
|
|||||||
const QString &confirmation)
|
const QString &confirmation)
|
||||||
{
|
{
|
||||||
if (!m_authorizedUser) {
|
if (!m_authorizedUser) {
|
||||||
|
appendLog(QStringLiteral("PASSWORD refused: unauthorized user"));
|
||||||
emit passwordChangeFinished(false,
|
emit passwordChangeFinished(false,
|
||||||
QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
|
QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_busy) {
|
if (m_busy) {
|
||||||
|
emit passwordChangeFinished(false,
|
||||||
|
QStringLiteral("Une opération de configuration est déjà en cours."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +254,6 @@ void ProvisioningBackend::changePassword(const QString ¤tPassword,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Les helpers utilisent un protocole interne délimité par des retours ligne.
|
|
||||||
// Un secret interactif passwd/FIDO2 ne doit donc jamais en contenir.
|
|
||||||
if (containsLineBreak(currentPassword) || containsLineBreak(newPassword)
|
if (containsLineBreak(currentPassword) || containsLineBreak(newPassword)
|
||||||
|| containsLineBreak(confirmation)) {
|
|| containsLineBreak(confirmation)) {
|
||||||
emit passwordChangeFinished(false,
|
emit passwordChangeFinished(false,
|
||||||
@@ -196,16 +261,15 @@ void ProvisioningBackend::changePassword(const QString ¤tPassword,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifie avant toute modification que l'état de provisioning pourra
|
|
||||||
// effectivement être persisté dans le HOME de l'utilisateur.
|
|
||||||
if (!ensureStateWritable()) {
|
if (!ensureStateWritable()) {
|
||||||
|
appendLog(QStringLiteral("PASSWORD refused: state directory is not writable"));
|
||||||
emit passwordChangeFinished(false,
|
emit passwordChangeFinished(false,
|
||||||
QStringLiteral("Impossible d'enregistrer l'état de configuration dans votre profil. Contactez le service informatique."));
|
QStringLiteral("Impossible d'enregistrer l'état de configuration dans votre profil. Contactez le service informatique."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startHelper(Operation::Password,
|
startHelper(Operation::Password,
|
||||||
QStringLiteral(PASSWORD_HELPER_PATH),
|
QString::fromUtf8(NIXOS_WORKSTATIONS_PASSWORD_HELPER_PATH),
|
||||||
currentPassword,
|
currentPassword,
|
||||||
newPassword,
|
newPassword,
|
||||||
confirmation);
|
confirmation);
|
||||||
@@ -216,12 +280,15 @@ void ProvisioningBackend::changePin(const QString ¤tPin,
|
|||||||
const QString &confirmation)
|
const QString &confirmation)
|
||||||
{
|
{
|
||||||
if (!m_authorizedUser) {
|
if (!m_authorizedUser) {
|
||||||
|
appendLog(QStringLiteral("PIN refused: unauthorized user"));
|
||||||
emit pinChangeFinished(false,
|
emit pinChangeFinished(false,
|
||||||
QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
|
QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_busy) {
|
if (m_busy) {
|
||||||
|
emit pinChangeFinished(false,
|
||||||
|
QStringLiteral("Une opération de configuration est déjà en cours."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,13 +324,14 @@ void ProvisioningBackend::changePin(const QString ¤tPin,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ensureStateWritable()) {
|
if (!ensureStateWritable()) {
|
||||||
|
appendLog(QStringLiteral("PIN refused: state directory is not writable"));
|
||||||
emit pinChangeFinished(false,
|
emit pinChangeFinished(false,
|
||||||
QStringLiteral("Impossible d'enregistrer l'état de configuration dans votre profil. Contactez le service informatique."));
|
QStringLiteral("Impossible d'enregistrer l'état de configuration dans votre profil. Contactez le service informatique."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startHelper(Operation::Pin,
|
startHelper(Operation::Pin,
|
||||||
QStringLiteral(PIN_HELPER_PATH),
|
QString::fromUtf8(NIXOS_WORKSTATIONS_PIN_HELPER_PATH),
|
||||||
currentPin,
|
currentPin,
|
||||||
newPin,
|
newPin,
|
||||||
confirmation);
|
confirmation);
|
||||||
@@ -275,8 +343,11 @@ void ProvisioningBackend::startHelper(Operation operation,
|
|||||||
const QString &secondSecret,
|
const QString &secondSecret,
|
||||||
const QString &thirdSecret)
|
const QString &thirdSecret)
|
||||||
{
|
{
|
||||||
if (!QFileInfo::exists(helperPath)) {
|
const QFileInfo helperInfo(helperPath);
|
||||||
const QString message = QStringLiteral("Le composant système requis est introuvable.");
|
if (!helperInfo.exists() || !helperInfo.isFile() || !helperInfo.isExecutable()) {
|
||||||
|
appendLog(operationName(operation)
|
||||||
|
+ QStringLiteral(" failed: helper missing or not executable"));
|
||||||
|
const QString message = QStringLiteral("Le composant système requis est introuvable ou inutilisable.");
|
||||||
if (operation == Operation::Password) {
|
if (operation == Operation::Password) {
|
||||||
emit passwordChangeFinished(false, message);
|
emit passwordChangeFinished(false, message);
|
||||||
} else {
|
} else {
|
||||||
@@ -285,85 +356,122 @@ void ProvisioningBackend::startHelper(Operation operation,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_operation = operation;
|
|
||||||
setBusy(true);
|
|
||||||
|
|
||||||
m_process = new QProcess(this);
|
|
||||||
m_process->setProgram(helperPath);
|
|
||||||
m_process->setProcessChannelMode(QProcess::SeparateChannels);
|
|
||||||
|
|
||||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
|
||||||
environment.insert(QStringLiteral("LC_ALL"), QStringLiteral("C"));
|
|
||||||
environment.insert(QStringLiteral("LANG"), QStringLiteral("C"));
|
|
||||||
m_process->setProcessEnvironment(environment);
|
|
||||||
|
|
||||||
auto *timeout = new QTimer(m_process);
|
|
||||||
timeout->setSingleShot(true);
|
|
||||||
timeout->setInterval(kHelperTimeoutMs);
|
|
||||||
|
|
||||||
connect(timeout, &QTimer::timeout, m_process, [process = m_process]() {
|
|
||||||
if (process->state() != QProcess::NotRunning) {
|
|
||||||
process->kill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
QByteArray first = firstSecret.toUtf8();
|
QByteArray first = firstSecret.toUtf8();
|
||||||
QByteArray second = secondSecret.toUtf8();
|
QByteArray second = secondSecret.toUtf8();
|
||||||
QByteArray third = thirdSecret.toUtf8();
|
QByteArray third = thirdSecret.toUtf8();
|
||||||
|
|
||||||
connect(m_process, &QProcess::started, this,
|
m_pendingPayload.clear();
|
||||||
[this, first = std::move(first), second = std::move(second),
|
m_pendingPayload.reserve(first.size() + second.size() + third.size() + 3);
|
||||||
third = std::move(third), timeout]() mutable {
|
m_pendingPayload += first;
|
||||||
QByteArray payload;
|
m_pendingPayload += '\n';
|
||||||
payload.reserve(first.size() + second.size() + third.size() + 3);
|
m_pendingPayload += second;
|
||||||
payload += first;
|
m_pendingPayload += '\n';
|
||||||
payload += '\n';
|
m_pendingPayload += third;
|
||||||
payload += second;
|
m_pendingPayload += '\n';
|
||||||
payload += '\n';
|
|
||||||
payload += third;
|
|
||||||
payload += '\n';
|
|
||||||
|
|
||||||
m_process->write(payload);
|
|
||||||
m_process->closeWriteChannel();
|
|
||||||
|
|
||||||
// Réduit la durée de vie des copies explicites côté backend.
|
|
||||||
secureClear(first);
|
secureClear(first);
|
||||||
secureClear(second);
|
secureClear(second);
|
||||||
secureClear(third);
|
secureClear(third);
|
||||||
secureClear(payload);
|
|
||||||
timeout->start();
|
m_helperTimedOut = false;
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
auto *process = new QProcess(this);
|
||||||
|
m_process = process;
|
||||||
|
process->setProgram(helperPath);
|
||||||
|
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||||
|
|
||||||
|
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||||
|
environment.insert(QStringLiteral("LC_ALL"), QStringLiteral("C"));
|
||||||
|
environment.insert(QStringLiteral("LANG"), QStringLiteral("C"));
|
||||||
|
process->setProcessEnvironment(environment);
|
||||||
|
|
||||||
|
auto *timeout = new QTimer(process);
|
||||||
|
timeout->setSingleShot(true);
|
||||||
|
timeout->setInterval(kHelperTimeoutMs);
|
||||||
|
|
||||||
|
connect(timeout, &QTimer::timeout, process,
|
||||||
|
[this, process, operation]() {
|
||||||
|
if (m_process != process || process->state() == QProcess::NotRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_helperTimedOut = true;
|
||||||
|
appendLog(operationName(operation) + QStringLiteral(" timeout"));
|
||||||
|
process->kill();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_process, &QProcess::errorOccurred, this,
|
connect(process, &QProcess::started, this,
|
||||||
[this](QProcess::ProcessError error) {
|
[this, process, timeout, operation]() {
|
||||||
if (error != QProcess::FailedToStart) {
|
if (m_process != process) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qint64 expected = m_pendingPayload.size();
|
||||||
|
const qint64 written = process->write(m_pendingPayload);
|
||||||
|
process->closeWriteChannel();
|
||||||
|
secureClear(m_pendingPayload);
|
||||||
|
|
||||||
|
if (written != expected) {
|
||||||
|
appendLog(operationName(operation)
|
||||||
|
+ QStringLiteral(" warning: incomplete helper input write"));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLog(operationName(operation) + QStringLiteral(" helper started"));
|
||||||
|
timeout->start();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(process, &QProcess::errorOccurred, this,
|
||||||
|
[this, process, timeout, operation](QProcess::ProcessError error) {
|
||||||
|
if (m_process != process || error != QProcess::FailedToStart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout->stop();
|
||||||
|
secureClear(m_pendingPayload);
|
||||||
|
appendLog(operationName(operation)
|
||||||
|
+ QStringLiteral(" failed to start: ")
|
||||||
|
+ process->errorString());
|
||||||
|
|
||||||
|
m_process = nullptr;
|
||||||
|
process->deleteLater();
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
|
||||||
const QString message = QStringLiteral("Impossible de démarrer le composant de configuration.");
|
const QString message = QStringLiteral("Impossible de démarrer le composant de configuration.");
|
||||||
if (m_operation == Operation::Password) {
|
if (operation == Operation::Password) {
|
||||||
emit passwordChangeFinished(false, message);
|
emit passwordChangeFinished(false, message);
|
||||||
} else {
|
} else {
|
||||||
emit pinChangeFinished(false, message);
|
emit pinChangeFinished(false, message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_process,
|
connect(process,
|
||||||
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
|
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
|
||||||
this,
|
this,
|
||||||
[this, timeout](int exitCode, QProcess::ExitStatus exitStatus) {
|
[this, process, timeout, operation](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||||
timeout->stop();
|
if (m_process != process) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const QByteArray stderrData = m_process->readAllStandardError();
|
timeout->stop();
|
||||||
const QByteArray stdoutData = m_process->readAllStandardOutput();
|
secureClear(m_pendingPayload);
|
||||||
const bool success = exitStatus == QProcess::NormalExit && exitCode == 0;
|
|
||||||
const Operation operation = m_operation;
|
QByteArray stderrData = process->readAllStandardError();
|
||||||
|
QByteArray stdoutData = process->readAllStandardOutput();
|
||||||
|
QByteArray combinedOutput;
|
||||||
|
combinedOutput.reserve(stderrData.size() + stdoutData.size() + 1);
|
||||||
|
combinedOutput += stderrData;
|
||||||
|
combinedOutput += '\n';
|
||||||
|
combinedOutput += stdoutData;
|
||||||
|
|
||||||
|
const bool timedOut = m_helperTimedOut;
|
||||||
|
const bool helperSucceeded = !timedOut
|
||||||
|
&& exitStatus == QProcess::NormalExit
|
||||||
|
&& exitCode == 0;
|
||||||
|
|
||||||
QString message;
|
QString message;
|
||||||
bool markerCreated = false;
|
bool markerCreated = false;
|
||||||
|
|
||||||
if (success) {
|
if (helperSucceeded) {
|
||||||
if (operation == Operation::Password) {
|
if (operation == Operation::Password) {
|
||||||
markerCreated = createMarker(passwordMarker());
|
markerCreated = createMarker(passwordMarker());
|
||||||
message = markerCreated
|
message = markerCreated
|
||||||
@@ -376,15 +484,26 @@ void ProvisioningBackend::startHelper(Operation operation,
|
|||||||
: QStringLiteral("Le PIN a été modifié, mais l'état local n'a pas pu être enregistré. Contactez le service informatique avant de fermer la session.");
|
: QStringLiteral("Le PIN a été modifié, mais l'état local n'a pas pu être enregistré. Contactez le service informatique avant de fermer la session.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message = safeMessageForFailure(operation, exitCode, stderrData + stdoutData);
|
message = safeMessageForFailure(operation, exitCode, combinedOutput, timedOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_process->deleteLater();
|
appendLog(operationName(operation)
|
||||||
|
+ QStringLiteral(" helper finished exitCode=%1 normalExit=%2 timeout=%3 marker=%4")
|
||||||
|
.arg(exitCode)
|
||||||
|
.arg(exitStatus == QProcess::NormalExit ? QStringLiteral("yes") : QStringLiteral("no"))
|
||||||
|
.arg(timedOut ? QStringLiteral("yes") : QStringLiteral("no"))
|
||||||
|
.arg(markerCreated ? QStringLiteral("yes") : QStringLiteral("no")));
|
||||||
|
|
||||||
|
secureClear(stderrData);
|
||||||
|
secureClear(stdoutData);
|
||||||
|
secureClear(combinedOutput);
|
||||||
|
|
||||||
m_process = nullptr;
|
m_process = nullptr;
|
||||||
|
process->deleteLater();
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
refreshState();
|
refreshState();
|
||||||
|
|
||||||
const bool completed = success && markerCreated;
|
const bool completed = helperSucceeded && markerCreated;
|
||||||
if (operation == Operation::Password) {
|
if (operation == Operation::Password) {
|
||||||
emit passwordChangeFinished(completed, message);
|
emit passwordChangeFinished(completed, message);
|
||||||
} else {
|
} else {
|
||||||
@@ -392,31 +511,36 @@ void ProvisioningBackend::startHelper(Operation operation,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
m_process->start();
|
process->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ProvisioningBackend::safeMessageForFailure(Operation operation,
|
QString ProvisioningBackend::safeMessageForFailure(Operation operation,
|
||||||
int exitCode,
|
int exitCode,
|
||||||
const QByteArray &stderrData) const
|
const QByteArray &outputData,
|
||||||
|
bool timedOut) const
|
||||||
{
|
{
|
||||||
const QString output = QString::fromUtf8(stderrData).toLower();
|
const QString output = QString::fromUtf8(outputData).toLower();
|
||||||
|
|
||||||
if (exitCode == 124 || output.contains(QStringLiteral("timeout"))) {
|
if (timedOut || exitCode == 124 || output.contains(QStringLiteral("timeout"))) {
|
||||||
return QStringLiteral("L'opération a expiré. Vérifiez le périphérique puis réessayez.");
|
return QStringLiteral("L'opération a expiré. Vérifiez le périphérique puis réessayez.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation == Operation::Password) {
|
if (operation == Operation::Password) {
|
||||||
if (output.contains(QStringLiteral("current_rejected"))) {
|
if (output.contains(QStringLiteral("current_rejected"))
|
||||||
return QStringLiteral("Le mot de passe temporaire actuel est incorrect.");
|
|| output.contains(QStringLiteral("current_prompt_missing"))) {
|
||||||
|
return QStringLiteral("Le mot de passe temporaire actuel est incorrect ou n'a pas pu être vérifié.");
|
||||||
}
|
}
|
||||||
if (output.contains(QStringLiteral("new_rejected"))) {
|
if (output.contains(QStringLiteral("new_rejected"))) {
|
||||||
return QStringLiteral("Le système a refusé le nouveau mot de passe. Choisissez-en un autre puis réessayez.");
|
return QStringLiteral("Le système a refusé le nouveau mot de passe. Choisissez-en un autre puis réessayez.");
|
||||||
}
|
}
|
||||||
|
if (output.contains(QStringLiteral("input_error"))) {
|
||||||
|
return QStringLiteral("Le composant de changement de mot de passe n'a pas reçu les données attendues.");
|
||||||
|
}
|
||||||
return QStringLiteral("Le mot de passe n'a pas été modifié. Vérifiez le mot de passe actuel puis réessayez.");
|
return QStringLiteral("Le mot de passe n'a pas été modifié. Vérifiez le mot de passe actuel puis réessayez.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.contains(QStringLiteral("wrong_pin"))) {
|
if (output.contains(QStringLiteral("pin_not_configured"))) {
|
||||||
return QStringLiteral("Le PIN temporaire est incorrect. N'essayez pas au hasard : le nombre de tentatives FIDO2 est limité.");
|
return QStringLiteral("Aucun PIN FIDO2 actuel n'a été détecté sur la YubiKey. Contactez le service informatique pour initialiser la clé.");
|
||||||
}
|
}
|
||||||
if (output.contains(QStringLiteral("pin_auth_blocked"))) {
|
if (output.contains(QStringLiteral("pin_auth_blocked"))) {
|
||||||
return QStringLiteral("Les tentatives de PIN sont temporairement bloquées. Débranchez puis rebranchez la YubiKey avant de réessayer avec le bon PIN.");
|
return QStringLiteral("Les tentatives de PIN sont temporairement bloquées. Débranchez puis rebranchez la YubiKey avant de réessayer avec le bon PIN.");
|
||||||
@@ -424,12 +548,18 @@ QString ProvisioningBackend::safeMessageForFailure(Operation operation,
|
|||||||
if (output.contains(QStringLiteral("pin_blocked"))) {
|
if (output.contains(QStringLiteral("pin_blocked"))) {
|
||||||
return QStringLiteral("Le PIN FIDO2 de la YubiKey est bloqué. Contactez le service informatique ; ne réinitialisez pas la clé.");
|
return QStringLiteral("Le PIN FIDO2 de la YubiKey est bloqué. Contactez le service informatique ; ne réinitialisez pas la clé.");
|
||||||
}
|
}
|
||||||
|
if (output.contains(QStringLiteral("wrong_pin"))) {
|
||||||
|
return QStringLiteral("Le PIN temporaire est incorrect. N'essayez pas au hasard : le nombre de tentatives FIDO2 est limité.");
|
||||||
|
}
|
||||||
if (output.contains(QStringLiteral("no_yubikey"))) {
|
if (output.contains(QStringLiteral("no_yubikey"))) {
|
||||||
return QStringLiteral("Aucune YubiKey compatible n'a été détectée. Branchez la clé puis réessayez.");
|
return QStringLiteral("Aucune YubiKey compatible n'a été détectée ou le système n'a pas les droits d'accès nécessaires.");
|
||||||
}
|
}
|
||||||
if (output.contains(QStringLiteral("pin_policy"))) {
|
if (output.contains(QStringLiteral("pin_policy"))) {
|
||||||
return QStringLiteral("Le nouveau PIN ne respecte pas la politique configurée sur cette YubiKey.");
|
return QStringLiteral("Le nouveau PIN ne respecte pas la politique configurée sur cette YubiKey.");
|
||||||
}
|
}
|
||||||
|
if (output.contains(QStringLiteral("input_error"))) {
|
||||||
|
return QStringLiteral("Le composant de configuration de la YubiKey n'a pas reçu les données attendues.");
|
||||||
|
}
|
||||||
|
|
||||||
return QStringLiteral("Le PIN YubiKey n'a pas été modifié. Vérifiez que la clé est branchée et que le PIN actuel est correct.");
|
return QStringLiteral("Le PIN YubiKey n'a pas été modifié. Vérifiez que la clé est branchée et que le PIN actuel est correct.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -17,7 +18,8 @@ class ProvisioningBackend final : public QObject
|
|||||||
Q_PROPERTY(bool authorizedUser READ authorizedUser CONSTANT)
|
Q_PROPERTY(bool authorizedUser READ authorizedUser CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ProvisioningBackend(QObject *parent = nullptr);
|
explicit ProvisioningBackend(QString targetUser, QObject *parent = nullptr);
|
||||||
|
~ProvisioningBackend() override;
|
||||||
|
|
||||||
bool busy() const { return m_busy; }
|
bool busy() const { return m_busy; }
|
||||||
bool passwordDone() const { return m_passwordDone; }
|
bool passwordDone() const { return m_passwordDone; }
|
||||||
@@ -27,6 +29,13 @@ public:
|
|||||||
QString targetUser() const { return m_targetUser; }
|
QString targetUser() const { return m_targetUser; }
|
||||||
bool authorizedUser() const { return m_authorizedUser; }
|
bool authorizedUser() const { return m_authorizedUser; }
|
||||||
|
|
||||||
|
QString stateDirectory() const;
|
||||||
|
QString diagnosticLogPath() const;
|
||||||
|
QString passwordMarker() const;
|
||||||
|
QString pinMarker() const;
|
||||||
|
|
||||||
|
static QString effectiveUserName();
|
||||||
|
|
||||||
Q_INVOKABLE void changePassword(const QString ¤tPassword,
|
Q_INVOKABLE void changePassword(const QString ¤tPassword,
|
||||||
const QString &newPassword,
|
const QString &newPassword,
|
||||||
const QString &confirmation);
|
const QString &confirmation);
|
||||||
@@ -57,22 +66,23 @@ private:
|
|||||||
void refreshState();
|
void refreshState();
|
||||||
bool ensureStateWritable();
|
bool ensureStateWritable();
|
||||||
bool createMarker(const QString &path);
|
bool createMarker(const QString &path);
|
||||||
QString stateDirectory() const;
|
void appendLog(const QString &message) const;
|
||||||
QString passwordMarker() const;
|
|
||||||
QString pinMarker() const;
|
|
||||||
QString safeMessageForFailure(Operation operation,
|
QString safeMessageForFailure(Operation operation,
|
||||||
int exitCode,
|
int exitCode,
|
||||||
const QByteArray &stderrData) const;
|
const QByteArray &output,
|
||||||
|
bool timedOut) const;
|
||||||
|
|
||||||
static QString effectiveUserName();
|
|
||||||
static void secureClear(QByteArray &data);
|
static void secureClear(QByteArray &data);
|
||||||
|
static QString operationName(Operation operation);
|
||||||
|
|
||||||
bool m_busy = false;
|
bool m_busy = false;
|
||||||
bool m_passwordDone = false;
|
bool m_passwordDone = false;
|
||||||
bool m_pinDone = false;
|
bool m_pinDone = false;
|
||||||
bool m_authorizedUser = false;
|
bool m_authorizedUser = false;
|
||||||
|
bool m_helperTimedOut = false;
|
||||||
QString m_currentUser;
|
QString m_currentUser;
|
||||||
QString m_targetUser;
|
QString m_targetUser;
|
||||||
QProcess *m_process = nullptr;
|
QProcess *m_process = nullptr;
|
||||||
Operation m_operation = Operation::Password;
|
QByteArray m_pendingPayload;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user