tFetch and Create safe(1) secrets using safe-agent - sailfish-safe - Sailfish frontend for safe(1)
 (HTM) git clone git://git.z3bra.org/sailfish-safe.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit c47b28af98cf0e370d42100ae736f71802f0cf50
 (DIR) parent dd3e65b8ef4a24517f1c7f62132fab50154f660f
 (HTM) Author: Willy Goiffon <contact@z3bra.org>
       Date:   Tue, 13 Jul 2021 15:52:34 +0200
       
       Fetch and Create safe(1) secrets using safe-agent
       
       Diffstat:
         M qml/components/PasswordDelegate.qml |      24 ++++++++++--------------
         M src/passwordprovider.cpp            |      45 +++++++++++---------------------
         M src/passwordprovider.h              |       1 -
         M src/passwordsmodel.cpp              |      17 +++++------------
         M src/safe.cpp                        |      89 ++++++++++++++++++++++++++-----
         M src/safe.h                          |      44 ++++++++++++++++++++++---------
       
       6 files changed, 137 insertions(+), 83 deletions(-)
       ---
 (DIR) diff --git a/qml/components/PasswordDelegate.qml b/qml/components/PasswordDelegate.qml
       t@@ -109,20 +109,16 @@ ListItem {
                    listItem.folderSelected()
                } else {
                    modelData.password.requestPassword()
       -            var dialog = pageStack.push(Qt.resolvedUrl("../pages/PassphraseRequester.qml"),
       -                                        { "requester": modelData.password })
       -            dialog.done.connect(function() {
       -                listItem.password = modelData.password
       -                listItem.password.validChanged.connect(function() {
       -                    if (listItem.password.valid) {
       -                        remorse.execute(listItem, qsTr("Password will expire"),
       -                                        function() {
       -                                            if (listItem.password) {
       -                                                listItem.password.expirePassword();
       -                                            }
       -                                        }, listItem.password.defaultTimeout);
       -                    }
       -                });
       +            listItem.password = modelData.password
       +            listItem.password.validChanged.connect(function() {
       +                if (listItem.password.valid) {
       +                    remorse.execute(listItem, qsTr("Password will expire"),
       +                        function() {
       +                            if (listItem.password) {
       +                                listItem.password.expirePassword();
       +                            }
       +                    }, listItem.password.defaultTimeout);
       +                }
                    });
                }
            }
 (DIR) diff --git a/src/passwordprovider.cpp b/src/passwordprovider.cpp
       t@@ -93,6 +93,21 @@ void PasswordProvider::requestPassword()
            Q_EMIT validChanged();
            Q_EMIT passwordChanged();
        
       +    auto *job = Safe::decrypt(mPath);
       +    connect(job, &Safe::DecryptTask::finished,
       +        this, [this, job]() {
       +            if (job->error()) {
       +                qWarning() << "Failed to unlock safe: " << job->errorString();
       +                setError(job->errorString());
       +                return;
       +            }
       +        // .split() always return one argument, so it's used here to remove
       +        // trailing \n if any, or keep the whole content otherwise
       +        const QStringList lines = job->content().split(QLatin1Char('\n'));
       +        setPassword(lines[0]);
       +        qDebug() << "requestPassword() got: " << lines[0];
       +    });
       +
            mTimer.setInterval(PasswordTimeoutUpdateInterval);
            connect(&mTimer, &QTimer::timeout,
                    this, [this]() {
       t@@ -102,8 +117,6 @@ void PasswordProvider::requestPassword()
                            expirePassword();
                        }
                    });
       -
       -
        }
        
        int PasswordProvider::timeout() const
       t@@ -137,34 +150,6 @@ void PasswordProvider::cancel()
            setError(tr("Cancelled by user."));
        }
        
       -void PasswordProvider::setPassphrase(const QString &passphrase)
       -{
       -    const QString root =  qEnvironmentVariableIsSet(SAFE_DIR)
       -            ? QString::fromUtf8(qgetenv(SAFE_DIR))
       -            : QStringLiteral("%1/.safe.d").arg(QDir::homePath());
       -
       -    const QString socket =  qEnvironmentVariableIsSet(SAFE_SOCK)
       -            ? QString::fromUtf8(qgetenv(SAFE_SOCK))
       -            : QStringLiteral("%1/.safe.sock").arg(QDir::homePath());
       -
       -    QFile safeAgentSocket(socket);
       -    if (!safeAgentSocket.exists()) {
       -        qWarning() << "Missing socket file (" << safeAgentSocket.fileName() << ")";
       -        return;
       -    }
       -    /*
       -    auto *job = Safe::unlock(passphrase);
       -    connect(job, &Safe::UnlockTask::finished,
       -            this, [this, job]() {
       -                if (job->error()) {
       -                    qWarning() << "Failed to unlock safe: " << job->errorString();
       -                    setError(job->errorString());
       -                    return;
       -                }
       -            });
       -            */
       -}
       -
        void PasswordProvider::removePasswordFromClipboard(const QString &password)
        {
            // Clear the WS clipboard itself
 (DIR) diff --git a/src/passwordprovider.h b/src/passwordprovider.h
       t@@ -50,7 +50,6 @@ public:
        public Q_SLOTS:
            void requestPassword();
            void cancel();
       -    void setPassphrase(const QString &passphrase);
            void expirePassword();
        
        Q_SIGNALS:
 (DIR) diff --git a/src/passwordsmodel.cpp b/src/passwordsmodel.cpp
       t@@ -186,7 +186,7 @@ QVariant PasswordsModel::data(const QModelIndex &index, int role) const
                return node->fullName();
            case PasswordRole:
                if (!node->provider) {
       -            node->provider = new PasswordProvider(node->path());
       +            node->provider = new PasswordProvider(node->fullName());
                }
                return QVariant::fromValue(node->provider.data());
            case HasPasswordRole:
       t@@ -226,27 +226,20 @@ void PasswordsModel::populateDir(const QDir& dir, Node *parent)
        void PasswordsModel::addPassword(const QModelIndex &parent, const QString &name,
                                         const QString &password, const QString &extras)
        {
       -    auto node = this->node(parent);
       -    if (!node) {
       -        node = mRoot;
       -    }
       -
       -    // Escape forward slash to avoid the name "escaping" the current folder
       -    QString safeName = name;
       -    safeName.replace(QLatin1Char('/'), QLatin1Char(' '));
        
            QString data = password;
       +    QString secret = name;
            if (!extras.isEmpty()) {
                data += QStringLiteral("\n%1").arg(extras);
            }
        
       -    auto *task = Safe::encrypt(QStringLiteral("%1/%2").arg(node->path(), safeName), data);
       +    auto *task = Safe::encrypt(secret, data);
            connect(task, &Safe::EncryptTask::finished,
       -            this, [safeName, task]() {
       +            this, [secret, task]() {
                        if (task->error())  {
                            qWarning() << "Error:" << task->errorString();
                            return;
                        }
       -                qDebug() << "Successfully encrypted password for" << safeName;
       +                qDebug() << "Successfully stored secret " << secret;
                    });
        }
 (DIR) diff --git a/src/safe.cpp b/src/safe.cpp
       t@@ -9,21 +9,23 @@
        #include <QtConcurrent>
        #include <QFutureWatcher>
        
       -namespace {
       +#define SAFE_DIR "SAFE_DIR"
       +#define SAFE_PID "SAFE_PID"
       +#define SAFE_SOCK "SAFE_SOCK"
       +#define SAFE_ASKPASS "SAFE_ASKPASS"
        
       -struct SafeExecutable {
       -    SafeExecutable(const QString &path)
       -        : path(path)
       -    {}
       -    QString path = {};
       -};
       +namespace {
       +} // namespace
        
       -SafeExecutable findSafeExecutable()
       +Safe::LockTask *Safe::lock()
        {
       -    return QStandardPaths::findExecutable(QStringLiteral("safe"));
       +    return new LockTask();
        }
        
       -} // namespace
       +Safe::UnlockTask *Safe::unlock(const QString &passphrase)
       +{
       +    return new UnlockTask(passphrase);
       +}
        
        Safe::EncryptTask *Safe::encrypt(const QString &file, const QString &content)
        {
       t@@ -68,15 +70,74 @@ void Safe::Task::start()
            watcher->setFuture(future);
        }
        
       +Safe::LockTask::LockTask()
       +{}
       +
       +void Safe::LockTask::run()
       +{
       +    if (qEnvironmentVariableIsSet(SAFE_PID))
       +        return;
       +
       +    const auto kill = QStandardPaths::findExecutable(QStringLiteral("kill"));
       +    const auto agent = QString::fromUtf8(qgetenv(SAFE_PID));
       +
       +    QProcess process;
       +    process.setProgram(kill);
       +    process.setArguments({
       +        QStringLiteral("-USR1"),
       +        QStringLiteral("%1").arg(agent)
       +    });
       +    process.start();
       +    process.waitForStarted();
       +    process.waitForFinished();
       +    if (process.exitCode() != 0) {
       +        const auto err = process.readAllStandardError();
       +        qWarning() << "Failed to forget key:" << err;
       +        setError(QString::fromUtf8(err));
       +    }
       +}
       +
       +Safe::UnlockTask::UnlockTask(const QString &passphrase)
       +    : mPassphrase(passphrase)
       +{}
       +
       +void Safe::UnlockTask::run()
       +{
       +    const QString safe = QStandardPaths::findExecutable(QStringLiteral("safe"));
       +
       +    if (!qEnvironmentVariableIsSet(SAFE_PID)) {
       +        qWarning() << "Agent is not running";
       +        return;
       +    }
       +
       +    if (!qEnvironmentVariableIsSet(SAFE_SOCK)) {
       +        qWarning() << "Agent is not found";
       +        return;
       +    }
       +
       +    QProcess process;
       +    process.setProgram(safe);
       +    process.setArguments({QStringLiteral("-r")});
       +    process.start();
       +    process.waitForStarted();
       +    process.write(mPassphrase.toUtf8());
       +    process.waitForFinished();
       +    if (process.exitCode() != 0) {
       +        const auto err = process.readAllStandardError();
       +        qWarning() << "Failed to unlock safe:" << err;
       +        setError(QString::fromUtf8(err));
       +    }
       +}
       +
        Safe::EncryptTask::EncryptTask(const QString &file, const QString &content)
            : mFile(file), mContent(content)
        {}
        
        void Safe::EncryptTask::run()
        {
       -    const auto safe = findSafeExecutable();
       +    const QString safe = QStandardPaths::findExecutable(QStringLiteral("safe"));
            QProcess process;
       -    process.setProgram(safe.path);
       +    process.setProgram(safe);
            process.setArguments({
                QStringLiteral("-a"),
                QStringLiteral("%1").arg(mFile)
       t@@ -104,9 +165,9 @@ QString Safe::DecryptTask::content() const
        
        void Safe::DecryptTask::run()
        {
       -    const auto safe = findSafeExecutable();
       +    const QString safe = QStandardPaths::findExecutable(QStringLiteral("safe"));
            QProcess process;
       -    process.setProgram(safe.path);
       +    process.setProgram(safe);
            process.setArguments({QStringLiteral("%1").arg(mFile)});
            process.start();
            process.waitForStarted();
 (DIR) diff --git a/src/safe.h b/src/safe.h
       t@@ -6,21 +6,13 @@
        
        namespace Safe
        {
       +class LockTask;
       +class UnlockTask;
        class DecryptTask;
        class EncryptTask;
        
       -struct Key {
       -    enum class Trust {
       -        Unknown = 1,
       -        Never = 2,
       -        Marginal = 3,
       -        Full = 4,
       -        Ultimate = 5
       -    };
       -
       -    QString id;
       -};
       -
       +LockTask *lock();
       +UnlockTask *unlock(const QString &passphrase);
        DecryptTask *decrypt(const QString &file);
        EncryptTask *encrypt(const QString &data, const QString &file);
        
       t@@ -48,6 +40,34 @@ private:
            QString mError;
        };
        
       +class LockTask : public Task {
       +    Q_OBJECT
       +    friend LockTask *Safe::lock();
       +public:
       +    QString content() const;
       +
       +protected:
       +    void run() override;
       +
       +private:
       +    LockTask();
       +};
       +
       +class UnlockTask : public Task {
       +    Q_OBJECT
       +    friend UnlockTask *Safe::unlock(const QString &);
       +public:
       +    QString content() const;
       +
       +protected:
       +    void run() override;
       +
       +private:
       +    UnlockTask(const QString &passphrase);
       +
       +    QString mPassphrase;
       +};
       +
        class DecryptTask : public Task {
            Q_OBJECT
            friend DecryptTask *Safe::decrypt(const QString &);