From c32ed4ac4b6ebcbc2d7de2847b9994af8c4a63e7 Mon Sep 17 00:00:00 2001 From: Denis Danilov Date: Tue, 5 Feb 2019 22:53:55 +0100 Subject: [PATCH 1/6] update building instructions --- BUILDING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BUILDING.md b/BUILDING.md index e4e83fff1..d0b05072a 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -51,12 +51,16 @@ hacking on Quaternion _and_ libQMatrixClient at the same time. - any build system that works with CMake should be fine: GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. - libQMatrixClient development files (from your package management system), or prebuilt libQMatrixClient (see "Getting the source code" above). +- optionaly [QtKeychain](https://github.com/frankosterfeld/qtkeychain) to store access tokens in libsecret keyring or similar providers. #### Linux Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to take a look at `CMakeLists.txt` to figure out which specific libraries Quaternion uses (or blindly run cmake and look at error messages). Note also that you'll need several Qt Quick plugins for Quaternion to work (without them, it will compile and run but won't show the messages timeline). In case of Xenial Xerus following line should get you everything necessary to build and run Quaternion: ```bash sudo apt-get install git cmake qtdeclarative5-dev qtdeclarative5-qtquick2-plugin qtdeclarative5-controls-plugin qml-module-qtquick-controls qml-module-qtquick-controls2 qtmultimedia5-dev ``` +```bash +sudo apt-get install qt5keychain-dev +``` On Fedora 26, the following command should be enough for building and running: ```bash dnf install git cmake qt5-qtdeclarative-devel qt5-qtquickcontrols qt5-qtquickcontrols2 qt5-qtmultimedia-devel From e7221ea6d0cada070d10e19370336abe464b346f Mon Sep 17 00:00:00 2001 From: Denis Danilov Date: Tue, 5 Feb 2019 22:43:17 +0100 Subject: [PATCH 2/6] add support for qtkeychain to store access tokens --- CMakeLists.txt | 14 +++++ client/mainwindow.cpp | 123 ++++++++++++++++++++++++++++++++++++++++++ client/mainwindow.h | 6 +++ 3 files changed, 143 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cdc237ca..b11f4cf2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,11 @@ else () endif () endif () +find_package(Qt5Keychain QUIET) +if (Qt5Keychain_FOUND) + set(USE_KEYCHAIN ON) +endif() + message( STATUS ) message( STATUS "=============================================================================" ) message( STATUS " Quaternion Build Information" ) @@ -121,6 +126,9 @@ endif () if (USE_QQUICKWIDGET) message( STATUS "Using QQuickWidget to render QML") endif(USE_QQUICKWIDGET) +if (USE_KEYCHAIN) + message( STATUS "Using Qt5Keychain ${Qt5Keychain_VERSION} at ${Qt5Keychain_DIR}") +endif () message( STATUS "=============================================================================" ) message( STATUS ) @@ -200,6 +208,12 @@ if (USE_QQUICKWIDGET) target_link_libraries(${APP_NAME} Qt5::QuickWidgets) endif() +if(USE_KEYCHAIN) + target_compile_definitions(${APP_NAME} PRIVATE USE_KEYCHAIN) + target_link_libraries(${APP_NAME} ${QTKEYCHAIN_LIBRARIES}) + include_directories(${QTKEYCHAIN_INCLUDE_DIR}) +endif() + # macOS specific config for bundling set_property(TARGET ${APP_NAME} PROPERTY MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in") diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 22f9ffe5c..9cfde28a7 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -34,6 +34,10 @@ #include #include +#ifdef USE_KEYCHAIN +#include +#endif + #include #include #include @@ -404,6 +408,15 @@ inline QString accessTokenFileName(const AccountSettings& account) } QByteArray MainWindow::loadAccessToken(const AccountSettings& account) +{ +#ifdef USE_KEYCHAIN + return loadAccessTokenFromKeyChain(account); +#else + return loadAccessTokenFromFile(account); +#endif +} + +QByteArray MainWindow::loadAccessTokenFromFile(const AccountSettings& account) { QFile accountTokenFile { accessTokenFileName(account) }; if (accountTokenFile.open(QFile::ReadOnly)) @@ -421,8 +434,70 @@ QByteArray MainWindow::loadAccessToken(const AccountSettings& account) return {}; } +#ifdef USE_KEYCHAIN +QByteArray MainWindow::loadAccessTokenFromKeyChain(const AccountSettings& account) +{ + // check for existing token file + auto accessToken = loadAccessTokenFromFile(account); + if(!accessToken.isEmpty()) + { + if(QMessageBox::warning(this, + tr("Access token file found"), + tr("Do you want to migrate the access token for %1 " + "from the file to keychain?").arg(account.userId()), + QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes) + { + qDebug() << "Migrating the access token from file to 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 file to keychain failed"; + QMessageBox::warning(this, + tr("Couldn't migrate access token"), + tr("Quaternion couldn't migrate access token %1 " + "from the file to keychain.").arg(account.userId()), + QMessageBox::Close); + } + } + } + + qDebug() << "Read access token from 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()) + { + qWarning() << "Could not read access token from keychain: " << qPrintable(job.errorString()); + return {}; + } + + return job.binaryData(); +} +#endif + bool MainWindow::saveAccessToken(const AccountSettings& account, const QByteArray& accessToken) +{ +#ifdef USE_KEYCHAIN + return saveAccessTokenToKeyChain(account, accessToken); +#else + return saveAccessTokenToFile(account, accessToken); +#endif +} + +bool MainWindow::saveAccessTokenToFile(const AccountSettings& account, + const QByteArray& accessToken) { // (Re-)Make a dedicated file for access_token. QFile accountTokenFile { accessTokenFileName(account) }; @@ -465,6 +540,36 @@ bool MainWindow::saveAccessToken(const AccountSettings& account, return false; } +#ifdef USE_KEYCHAIN +bool MainWindow::saveAccessTokenToKeyChain(const AccountSettings& account, + const QByteArray& accessToken) +{ + qDebug() << "Save access token to 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 keychain: " << qPrintable(job.errorString()); + QMessageBox::warning(this, + tr("Couldn't save access token"), + tr("Quaternion couldn't save the access token to keychain." + " You're logged in but will have to provide your password" + " again when you restart the application."), + QMessageBox::Close); + return false; + } + + return true; +} +#endif + void MainWindow::enableDebug() { chatRoomWidget->enableDebug(); @@ -788,6 +893,24 @@ void MainWindow::logout(Connection* c) QFile(accessTokenFileName(AccountSettings(c->userId()))).remove(); +#ifdef USE_KEYCHAIN + QKeychain::DeletePasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(c->userId()); + QEventLoop loop; + QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + if (job.error()) + { + qWarning() << "Could not delete access token from keychain: " << qPrintable(job.errorString()); + QMessageBox::warning(this, + tr("Couldn't delete access token"), + tr("Quaternion couldn't delete the access token from keychain."), + QMessageBox::Close); + } +#endif + c->logout(); } diff --git a/client/mainwindow.h b/client/mainwindow.h index 04666c05f..a42f48606 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -116,8 +116,14 @@ class MainWindow: public QMainWindow void loadSettings(); void saveSettings() const; QByteArray loadAccessToken(const QMatrixClient::AccountSettings& account); + QByteArray loadAccessTokenFromFile(const QMatrixClient::AccountSettings& account); + QByteArray loadAccessTokenFromKeyChain(const QMatrixClient::AccountSettings &account); bool saveAccessToken(const QMatrixClient::AccountSettings& account, const QByteArray& accessToken); + bool saveAccessTokenToFile(const QMatrixClient::AccountSettings& account, + const QByteArray& accessToken); + bool saveAccessTokenToKeyChain(const QMatrixClient::AccountSettings& account, + const QByteArray& accessToken); Connection* chooseConnection(); void showMillisToRecon(Connection* c); }; From 5f6acd5330c084bc8511183b4c58316d43475cdc Mon Sep 17 00:00:00 2001 From: Denis Danilov Date: Sun, 10 Feb 2019 20:51:58 +0100 Subject: [PATCH 3/6] add qt5keychain to .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 00db7fbf2..50cb2dc5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ matrix: - qt57quickcontrols - qt57quickcontrols2 - qt57tools + - qt5keychain-dev - os: linux dist: trusty compiler: clang @@ -62,12 +63,14 @@ matrix: - qt512quickcontrols2 - qt512tools - qt512translations + - qt5keychain-dev - os: osx env: [ 'PATH=/usr/local/opt/qt/bin:$PATH"' ] addons: homebrew: packages: - qt5 + - qtkeychain before_script: - eval "${ENV_EVAL}" From 15906f0345da3330ba70f68c98f25dadeea8d174 Mon Sep 17 00:00:00 2001 From: Denis Danilov Date: Sun, 10 Feb 2019 22:57:47 +0100 Subject: [PATCH 4/6] mention dbus explicitly in CMakeList.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b11f4cf2d..3ab3c8818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ foreach (FLAG "" all pedantic extra no-unused-parameter) endforeach () # Find the libraries -find_package(Qt5 5.7 REQUIRED Widgets Network Quick Qml Gui LinguistTools Multimedia) +find_package(Qt5 5.7 REQUIRED Widgets Network Quick Qml Gui LinguistTools Multimedia DBus) # Qt5_Prefix is only used to show Qt path in message() # Qt5_BinDir is where all the binary tools for Qt are if (QT_QMAKE_EXECUTABLE) From fb2d4c817ea122e49f337e5ca08cba7c846e5ec6 Mon Sep 17 00:00:00 2001 From: Denis Danilov Date: Tue, 12 Feb 2019 22:22:25 +0100 Subject: [PATCH 5/6] update building instructions --- BUILDING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BUILDING.md b/BUILDING.md index d0b05072a..8c135cf78 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -58,6 +58,7 @@ Just install things from the list above using your preferred package manager. If ```bash sudo apt-get install git cmake qtdeclarative5-dev qtdeclarative5-qtquick2-plugin qtdeclarative5-controls-plugin qml-module-qtquick-controls qml-module-qtquick-controls2 qtmultimedia5-dev ``` +To enable libsecret keyring support, install QtKeychain by ```bash sudo apt-get install qt5keychain-dev ``` From ae804c1b4baa0c76f0df9ae505574acbaad5c7e8 Mon Sep 17 00:00:00 2001 From: Denis Danilov Date: Tue, 12 Feb 2019 22:54:26 +0100 Subject: [PATCH 6/6] fallback to file if QtKeychain is there but failed to save the token --- client/mainwindow.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 9cfde28a7..8bfdbbe98 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -557,13 +557,16 @@ bool MainWindow::saveAccessTokenToKeyChain(const AccountSettings& account, if (job.error()) { qWarning() << "Could not save access token to keychain: " << qPrintable(job.errorString()); - QMessageBox::warning(this, + const auto button = QMessageBox::warning(this, tr("Couldn't save access token"), tr("Quaternion couldn't save the access token to keychain." - " You're logged in but will have to provide your password" - " again when you restart the application."), - QMessageBox::Close); - return false; + " Do you want to save the access token to file %1?").arg(accessTokenFileName(account)), + QMessageBox::Yes|QMessageBox::No); + if (button == QMessageBox::Yes) { + return saveAccessTokenToFile(account, accessToken); + } else { + return false; + } } return true;