From 56e0386d5bcfdbf0e36f971c32ac83bb8959687c Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 30 Jun 2019 22:12:43 +0800 Subject: [PATCH] Init QtKeychain support. --- .gitmodules | 3 ++ include/qtkeychain | 1 + spectral.pro | 9 ++++++ src/controller.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++---- src/controller.h | 8 +++-- 5 files changed, 89 insertions(+), 7 deletions(-) create mode 160000 include/qtkeychain diff --git a/.gitmodules b/.gitmodules index e4fa3ca3d..8ef0e2929 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "include/libQuotient"] path = include/libQuotient url = https://github.com/quotient-im/libQuotient.git +[submodule "include/qtkeychain"] + path = include/qtkeychain + url = https://github.com/frankosterfeld/qtkeychain.git diff --git a/include/qtkeychain b/include/qtkeychain new file mode 160000 index 000000000..e7118623a --- /dev/null +++ b/include/qtkeychain @@ -0,0 +1 @@ +Subproject commit e7118623a44485c173cc43e7386725c8cfec0842 diff --git a/spectral.pro b/spectral.pro index 9eb839282..058020ff2 100644 --- a/spectral.pro +++ b/spectral.pro @@ -20,6 +20,9 @@ isEmpty(USE_SYSTEM_SORTFILTERPROXYMODEL) { isEmpty(USE_SYSTEM_QMATRIXCLIENT) { USE_SYSTEM_QMATRIXCLIENT = false } +isEmpty(USE_SYSTEM_QTCHAIN) { + USE_SYSTEM_QTCHAIN = false +} $$USE_SYSTEM_QMATRIXCLIENT { PKGCONFIG += QMatrixClient @@ -33,6 +36,12 @@ $$USE_SYSTEM_SORTFILTERPROXYMODEL { message("Falling back to built-in SortFilterProxyModel.") include(include/SortFilterProxyModel/SortFilterProxyModel.pri) } +$$USE_SYSTEM_QTCHAIN { + LIBS += -lqt5keychain +} else { + message("Falling back to built-in QtKeychain.") + include(include/qtkeychain/qt5keychain.pri) +} INCLUDEPATH += include/hoedown HEADERS += \ diff --git a/src/controller.cpp b/src/controller.cpp index 4f52ebd7b..96f241df8 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -31,6 +31,8 @@ #include #include +#include + Controller::Controller(QObject* parent) : QObject(parent) { QApplication::setQuitOnLastWindowClosed(false); @@ -74,7 +76,7 @@ void Controller::loginWithCredentials(QString serverAddr, account.setHomeserver(conn->homeserver()); account.setDeviceId(conn->deviceId()); account.setDeviceName(deviceName); - if (!saveAccessToken(account, conn->accessToken())) + if (!saveAccessTokenToKeyChain(account, conn->accessToken())) qWarning() << "Couldn't save access token"; account.sync(); addConnection(conn); @@ -147,7 +149,7 @@ void Controller::invokeLogin() { for (const auto& accountId : accounts) { AccountSettings account{accountId}; if (!account.homeserver().isEmpty()) { - auto accessToken = loadAccessToken(account); + auto accessToken = loadAccessTokenFromKeyChain(account); auto c = new Connection(account.homeserver(), this); auto deviceName = account.deviceName(); @@ -170,7 +172,7 @@ void Controller::invokeLogin() { emit initiated(); } -QByteArray Controller::loadAccessToken(const AccountSettings& account) { +QByteArray Controller::loadAccessTokenFromFile(const AccountSettings& account) { QFile accountTokenFile{accessTokenFileName(account)}; if (accountTokenFile.open(QFile::ReadOnly)) { if (accountTokenFile.size() < 1024) @@ -186,8 +188,49 @@ QByteArray Controller::loadAccessToken(const AccountSettings& account) { return {}; } -bool Controller::saveAccessToken(const AccountSettings& account, - const QByteArray& accessToken) { +QByteArray Controller::loadAccessTokenFromKeyChain( + const AccountSettings& account) { + qDebug() << "Read the access token from the keychain for " + << account.userId(); + QKeychain::ReadPasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(account.userId()); + QEventLoop loop; + QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, + &QEventLoop::quit); + job.start(); + loop.exec(); + + if (job.error() == QKeychain::Error::NoError) { + return job.binaryData(); + } + + qWarning() << "Could not read the access token from the keychain: " + << qPrintable(job.errorString()); + // no access token from the keychain, try token file + auto accessToken = loadAccessTokenFromFile(account); + if (job.error() == QKeychain::Error::EntryNotFound) { + if (!accessToken.isEmpty()) { + qDebug() << "Migrating the access token from file to the keychain for " + << account.userId(); + bool removed = false; + bool saved = saveAccessTokenToKeyChain(account, accessToken); + if (saved) { + QFile accountTokenFile{accessTokenFileName(account)}; + removed = accountTokenFile.remove(); + } + if (!(saved && removed)) { + qDebug() << "Migrating the access token from the file to the keychain " + "failed"; + } + } + } + + return accessToken; +} + +bool Controller::saveAccessTokenToFile(const AccountSettings& account, + const QByteArray& accessToken) { // (Re-)Make a dedicated file for access_token. QFile accountTokenFile{accessTokenFileName(account)}; accountTokenFile.remove(); // Just in case @@ -203,6 +246,28 @@ bool Controller::saveAccessToken(const AccountSettings& account, return false; } +bool Controller::saveAccessTokenToKeyChain(const AccountSettings& account, + const QByteArray& accessToken) { + qDebug() << "Save the access token to the keychain for " << account.userId(); + QKeychain::WritePasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(account.userId()); + job.setBinaryData(accessToken); + QEventLoop loop; + QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, + &QEventLoop::quit); + job.start(); + loop.exec(); + + if (job.error()) { + qWarning() << "Could not save access token to the keychain: " + << qPrintable(job.errorString()); + return saveAccessTokenToFile(account, accessToken); + } + + return true; +} + void Controller::joinRoom(Connection* c, const QString& alias) { JoinRoomJob* joinRoomJob = c->joinRoom(alias); joinRoomJob->connect(joinRoomJob, &JoinRoomJob::failure, [=] { diff --git a/src/controller.h b/src/controller.h index cb25b1bf3..0d741155c 100644 --- a/src/controller.h +++ b/src/controller.h @@ -70,8 +70,12 @@ class Controller : public QObject { QVector m_connections; QPointer m_connection; - QByteArray loadAccessToken(const AccountSettings& account); - bool saveAccessToken(const AccountSettings& account, + QByteArray loadAccessTokenFromFile(const AccountSettings& account); + QByteArray loadAccessTokenFromKeyChain(const AccountSettings& account); + + bool saveAccessTokenToFile(const AccountSettings& account, + const QByteArray& accessToken); + bool saveAccessTokenToKeyChain(const AccountSettings& account, const QByteArray& accessToken); void loadSettings(); void saveSettings() const;