From f16eba45052172d405cd9e47f96d20cf565dce4f Mon Sep 17 00:00:00 2001 From: Johan Ouwerkerk Date: Sat, 29 Feb 2020 23:12:01 +0100 Subject: [PATCH] Plumbing for asking for passwords from QML This change introduces a model to signal the UI what password "scenario" is applicable and provides the necessary plumbing to expose it QML. --- src/app/keysmith.cpp | 5 ++ src/app/keysmith.h | 2 + src/main.cpp | 1 + src/model/CMakeLists.txt | 1 + src/model/password.cpp | 117 +++++++++++++++++++++++++++++++++++++++++++++++ src/model/password.h | 43 +++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 src/model/password.cpp create mode 100644 src/model/password.h diff --git a/src/app/keysmith.cpp b/src/app/keysmith.cpp index 0f4b9e6..e53df24 100644 --- a/src/app/keysmith.cpp +++ b/src/app/keysmith.cpp @@ -40,6 +40,11 @@ namespace app return new model::SimpleAccountListModel(storage(), this); } + model::PasswordRequest * Keysmith::passwordRequest(void) + { + return new model::PasswordRequest(storage()->secret(), this); + } + accounts::AccountStorage * Keysmith::storage(void) { if (!m_storage) { diff --git a/src/app/keysmith.h b/src/app/keysmith.h index ed89989..1df3924 100644 --- a/src/app/keysmith.h +++ b/src/app/keysmith.h @@ -7,6 +7,7 @@ #include "../account/account.h" #include "../model/accounts.h" +#include "../model/password.h" #include @@ -20,6 +21,7 @@ namespace app virtual ~Keysmith(); Q_INVOKABLE void copyToClipboard(const QString &text); Q_INVOKABLE model::SimpleAccountListModel * accountListModel(void); + Q_INVOKABLE model::PasswordRequest * passwordRequest(void); private: accounts::AccountStorage * storage(void); private: diff --git a/src/main.cpp b/src/main.cpp index 6e1526f..e5d3e24 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); qmlRegisterUncreatableType("Keysmith.Models", 1, 0, "AccountListModel", "Use the Keysmith singleton to obtain an AccountListModel"); + qmlRegisterUncreatableType("Keysmith.Models", 1, 0, "PasswordRequestModel", "Use the Keysmith singleton to obtain an PasswordRequestModel"); qmlRegisterUncreatableType("Keysmith.Models", 1, 0, "Account", "Use an AccountListModel from the Keysmith singleton to obtain an Account"); qmlRegisterType("Keysmith.Validators", 1, 0, "AccountNameValidator"); qmlRegisterType("Keysmith.Validators", 1, 0, "Base32SecretValidator"); diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt index 1466b43..09d4f58 100644 --- a/src/model/CMakeLists.txt +++ b/src/model/CMakeLists.txt @@ -5,6 +5,7 @@ set(model_SRCS accounts.cpp + password.cpp ) add_library(model_lib STATIC ${model_SRCS}) diff --git a/src/model/password.cpp b/src/model/password.cpp new file mode 100644 index 0000000..d87e9dc --- /dev/null +++ b/src/model/password.cpp @@ -0,0 +1,117 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk + */ +#include "password.h" + +#include "../secrets/secrets.h" +#include "../logging_p.h" + +KEYSMITH_LOGGER(logger, ".model.password") + +namespace model +{ + PasswordRequest::PasswordRequest(accounts::AccountSecret *secret, QObject *parent) : + QObject(parent), m_secret(secret), m_previous(false), m_haveKey(false), m_havePassword(false) + { + QObject::connect(m_secret, &accounts::AccountSecret::existingPasswordNeeded, this, &PasswordRequest::setPreviouslyDefined); + QObject::connect(m_secret, &accounts::AccountSecret::passwordAvailable, this, &PasswordRequest::setPasswordAvailable); + QObject::connect(m_secret, &accounts::AccountSecret::keyAvailable, this, &PasswordRequest::setKeyAvailable); + m_previous = secret->isExistingPasswordRequested(); + m_haveKey = secret->isKeyAvailable(); + m_havePassword = secret->isPasswordAvailable(); + } + + bool PasswordRequest::previouslyDefined(void) const + { + return m_previous; + } + + bool PasswordRequest::keyAvailable(void) const + { + return m_haveKey; + } + + bool PasswordRequest::passwordProvided(void) const + { + return m_havePassword; + } + + bool PasswordRequest::provideBothPasswords(QString password, QString other) + { + if (password != other || password.isEmpty()) { + qCDebug(logger) << "Not applying new password(s): passwords must match and must not be empty"; + return false; + } + + if (m_previous) { + qCDebug(logger) << "Ignoring new password(s): function should not be used to unlock existing account secrets"; + return false; + } + + std::optional params = secrets::KeyDerivationParameters::create(); + if (!params) { + qCDebug(logger) << "Unable apply new password(s): failed to create default key derivation parameters"; + return false; + } + + if (m_secret->answerNewPassword(password, *params)) { + other.fill(QLatin1Char('*'), -1); + return true; + } + + qCDebug(logger) << "Failed to apply new password(s)"; + return false; + } + + bool PasswordRequest::providePassword(QString password) + { + if (password.isEmpty()) { + qCDebug(logger) << "Not applying password: passwords must not be empty"; + return false; + } + + if (!m_previous) { + qCDebug(logger) << "Ignoring password: function should not be used to set up new account secrets"; + return false; + } + + if (m_secret->answerExistingPassword(password)) { + password.fill(QLatin1Char('*'), -1); + return true; + } + + qCDebug(logger) << "Failed to apply password for existing account secrets"; + return false; + } + + void PasswordRequest::setKeyAvailable(void) + { + if (!m_haveKey) { + m_haveKey = true; + Q_EMIT derivedKey(); + } else { + qCDebug(logger) << "Ignored signal: already marked key as available"; + } + } + + void PasswordRequest::setPasswordAvailable(void) + { + if (!m_havePassword) { + m_havePassword = true; + Q_EMIT passwordAccepted(); + } else { + qCDebug(logger) << "Ignored signal: already marked password as available"; + } + } + + void PasswordRequest::setPreviouslyDefined(void) + { + if (!m_previous) { + m_previous = true; + Q_EMIT passwordExists(); + } else { + qCDebug(logger) << "Ignored signal: already marked password for existing secrets"; + } + } +} diff --git a/src/model/password.h b/src/model/password.h new file mode 100644 index 0000000..2b63d91 --- /dev/null +++ b/src/model/password.h @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk + */ +#ifndef MODEL_PASSWORD_H +#define MODEL_PASSWORD_H + +#include "../account/keys.h" + +namespace model +{ + class PasswordRequest: public QObject + { + Q_OBJECT + Q_PROPERTY(bool previouslyDefined READ previouslyDefined NOTIFY passwordExists) + Q_PROPERTY(bool keyAvailable READ keyAvailable NOTIFY derivedKey) + Q_PROPERTY(bool passwordProvided READ passwordProvided NOTIFY passwordAccepted) + public: + explicit PasswordRequest(accounts::AccountSecret *secret, QObject *parent = nullptr); + bool previouslyDefined(void) const; + bool keyAvailable(void) const; + bool passwordProvided(void) const; + public: + Q_INVOKABLE bool providePassword(QString password); + Q_INVOKABLE bool provideBothPasswords(QString password, QString other); + Q_SIGNALS: + void passwordExists(void) const; + void passwordAccepted(void) const; + void derivedKey(void) const; + private Q_SLOTS: + void setKeyAvailable(void); + void setPasswordAvailable(void); + void setPreviouslyDefined(void); + private: + accounts::AccountSecret * m_secret; + private: + bool m_previous; + bool m_haveKey; + bool m_havePassword; + }; +} + +#endif