diff options
author | Tobias Fella <fella@posteo.de> | 2022-05-27 21:03:31 +0200 |
---|---|---|
committer | Tobias Fella <fella@posteo.de> | 2022-05-27 21:13:44 +0200 |
commit | 946cd4cb73f95526aa09777afc2a493610d9696d (patch) | |
tree | ee3bb02888addc2afc6d51528574d6b2cfa31b9d | |
parent | 729ba7da174eacc88bf9bd4e2e80eeab3fc92716 (diff) | |
download | libquotient-946cd4cb73f95526aa09777afc2a493610d9696d.tar.gz libquotient-946cd4cb73f95526aa09777afc2a493610d9696d.zip |
Load and store accounts in the keychain
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rw-r--r-- | lib/accountregistry.cpp | 67 | ||||
-rw-r--r-- | lib/accountregistry.h | 20 | ||||
-rw-r--r-- | lib/connection.cpp | 39 |
4 files changed, 123 insertions, 11 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 404ba87c..7900235c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,8 @@ find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModul get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") +find_package(${Qt}Keychain REQUIRED) + if (${PROJECT_NAME}_ENABLE_E2EE) find_package(${Qt} ${QtMinVersion} REQUIRED Sql) find_package(Olm 3.1.3 REQUIRED) @@ -108,7 +110,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) if (OpenSSL_FOUND) message(STATUS "Using OpenSSL ${OpenSSL_VERSION} at ${OpenSSL_DIR}") endif() - find_package(${Qt}Keychain REQUIRED) endif() @@ -309,14 +310,13 @@ if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm OpenSSL::Crypto OpenSSL::SSL - ${Qt}::Sql - ${QTKEYCHAIN_LIBRARIES}) + ${Qt}::Sql) set(FIND_DEPS "find_dependency(Olm) find_dependency(OpenSSL) find_dependency(${Qt}Sql)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${QTKEYCHAIN_LIBRARIES}) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) diff --git a/lib/accountregistry.cpp b/lib/accountregistry.cpp index 616b54b4..9b6114d9 100644 --- a/lib/accountregistry.cpp +++ b/lib/accountregistry.cpp @@ -5,7 +5,13 @@ #include "accountregistry.h" #include "connection.h" +#include <QtCore/QCoreApplication> +#if QT_VERSION_MAJOR >= 6 +# include <qt6keychain/keychain.h> +#else +# include <qt5keychain/keychain.h> +#endif using namespace Quotient; void AccountRegistry::add(Connection* a) @@ -15,6 +21,7 @@ void AccountRegistry::add(Connection* a) beginInsertRows(QModelIndex(), size(), size()); push_back(a); endInsertRows(); + Q_EMIT accountCountChanged(); } void AccountRegistry::drop(Connection* a) @@ -54,8 +61,6 @@ QHash<int, QByteArray> AccountRegistry::roleNames() const return { { AccountRole, "connection" } }; } - - Connection* AccountRegistry::get(const QString& userId) { for (const auto &connection : *this) { @@ -64,3 +69,61 @@ Connection* AccountRegistry::get(const QString& userId) } return nullptr; } + +QKeychain::ReadPasswordJob* AccountRegistry::loadAccessTokenFromKeychain(const QString& userId) +{ + qCDebug(MAIN) << "Reading access token from keychain for" << userId; + auto job = new QKeychain::ReadPasswordJob(qAppName(), this); + job->setKey(userId); + + connect(job, &QKeychain::Job::finished, this, [job]() { + if (job->error() == QKeychain::Error::NoError) { + return; + } + //TODO error handling + }); + job->start(); + + return job; +} + +void AccountRegistry::invokeLogin() +{ + const auto accounts = SettingsGroup("Accounts").childGroups(); + for (const auto& accountId : accounts) { + AccountSettings account{accountId}; + m_accountsLoading += accountId; + Q_EMIT accountsLoadingChanged(); + + if (!account.homeserver().isEmpty()) { + auto accessTokenLoadingJob = loadAccessTokenFromKeychain(account.userId()); + connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob]() { + AccountSettings account{accountId}; + if (accessTokenLoadingJob->error() != QKeychain::Error::NoError) { + //TODO error handling + return; + } + + auto connection = new Connection(account.homeserver()); + connect(connection, &Connection::connected, this, [connection] { + connection->loadState(); + connection->setLazyLoading(true); + + connection->syncLoop(); + }); + connect(connection, &Connection::loginError, this, [](const QString& error, const QString&) { + //TODO error handling + }); + connect(connection, &Connection::networkError, this, [](const QString& error, const QString&, int, int) { + //TODO error handling + }); + connection->assumeIdentity(account.userId(), accessTokenLoadingJob->binaryData(), account.deviceId()); + }); + } + } +} + +QStringList AccountRegistry::accountsLoading() const +{ + return m_accountsLoading; +} diff --git a/lib/accountregistry.h b/lib/accountregistry.h index 2f6dffdf..ab337303 100644 --- a/lib/accountregistry.h +++ b/lib/accountregistry.h @@ -5,15 +5,25 @@ #pragma once #include "quotient_export.h" +#include "settings.h" #include <QtCore/QAbstractListModel> +namespace QKeychain { +class ReadPasswordJob; +} + namespace Quotient { class Connection; class QUOTIENT_API AccountRegistry : public QAbstractListModel, private QVector<Connection*> { Q_OBJECT + /// Number of accounts that are currently fully loaded + Q_PROPERTY(int accountCount READ rowCount NOTIFY accountCountChanged) + /// List of accounts that are currently in some stage of being loaded (Reading token from keychain, trying to contact server, etc). + /// Can be used to inform the user or to show a login screen if size() == 0 and no accounts are loaded + Q_PROPERTY(QStringList accountsLoading READ accountsLoading NOTIFY accountsLoadingChanged) public: using const_iterator = QVector::const_iterator; using const_reference = QVector::const_reference; @@ -52,6 +62,16 @@ public: [[nodiscard]] int rowCount( const QModelIndex& parent = QModelIndex()) const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override; + + QStringList accountsLoading() const; + + void invokeLogin(); +Q_SIGNALS: + void accountCountChanged(); + void accountsLoadingChanged(); +private: + QKeychain::ReadPasswordJob* loadAccessTokenFromKeychain(const QString &userId); + QStringList m_accountsLoading; }; inline QUOTIENT_API AccountRegistry Accounts {}; diff --git a/lib/connection.cpp b/lib/connection.cpp index dba18cb1..526feb26 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -41,12 +41,12 @@ # include "e2ee/qolmsession.h" # include "e2ee/qolmutils.h" -# if QT_VERSION_MAJOR >= 6 -# include <qt6keychain/keychain.h> -# else -# include <qt5keychain/keychain.h> -# endif #endif // Quotient_E2EE_ENABLED +#if QT_VERSION_MAJOR >= 6 +# include <qt6keychain/keychain.h> +#else +# include <qt5keychain/keychain.h> +#endif #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) # include <QtCore/QCborValue> @@ -368,6 +368,32 @@ public: void saveDevicesList(); void loadDevicesList(); #endif + + void saveAccessTokenToKeychain() + { + qCDebug(MAIN) << "Saving access token to keychain for" << q->userId(); + auto job = new QKeychain::WritePasswordJob(qAppName()); + job->setAutoDelete(false); + job->setKey(q->userId()); + job->setBinaryData(data->accessToken()); + job->start(); + //TODO error handling + } + + void removeAccessTokenFromKeychain() + { + qCDebug(MAIN) << "Removing access token from keychain for" << q->userId(); + auto job = new QKeychain::DeletePasswordJob(qAppName()); + job->setAutoDelete(true); + job->setKey(q->userId()); + job->start(); + + auto pickleJob = new QKeychain::DeletePasswordJob(qAppName()); + pickleJob->setAutoDelete(true); + pickleJob->setKey(q->userId() + "-Pickle"_ls); + pickleJob->start(); + //TODO error handling + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -546,6 +572,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); + saveAccessTokenToKeychain(); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED @@ -684,6 +711,8 @@ void Connection::logout() disconnect(d->syncLoopConnection); d->data->setToken({}); emit loggedOut(); + SettingsGroup("Accounts").remove(userId()); + d->removeAccessTokenFromKeychain(); deleteLater(); } else { // logout() somehow didn't proceed - restore the session state emit stateChanged(); |