aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Fella <fella@posteo.de>2022-05-27 21:03:31 +0200
committerTobias Fella <fella@posteo.de>2022-05-27 21:13:44 +0200
commit946cd4cb73f95526aa09777afc2a493610d9696d (patch)
treeee3bb02888addc2afc6d51528574d6b2cfa31b9d
parent729ba7da174eacc88bf9bd4e2e80eeab3fc92716 (diff)
downloadlibquotient-946cd4cb73f95526aa09777afc2a493610d9696d.tar.gz
libquotient-946cd4cb73f95526aa09777afc2a493610d9696d.zip
Load and store accounts in the keychain
-rw-r--r--CMakeLists.txt8
-rw-r--r--lib/accountregistry.cpp67
-rw-r--r--lib/accountregistry.h20
-rw-r--r--lib/connection.cpp39
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();