Skip to content

Commit

Permalink
Merge pull request #484 from ddanilov/keychain
Browse files Browse the repository at this point in the history
 add support for qtkeychain to store access tokens
  • Loading branch information
KitsuneRal authored Feb 17, 2019
2 parents 373e2b5 + ae804c1 commit d1c79f1
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ matrix:
- qt57quickcontrols
- qt57quickcontrols2
- qt57tools
- qt5keychain-dev
- os: linux
dist: trusty
compiler: clang
Expand Down Expand Up @@ -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}"
Expand Down
5 changes: 5 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,17 @@ 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
```
To enable libsecret keyring support, install QtKeychain by
```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
Expand Down
16 changes: 15 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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" )
Expand Down Expand Up @@ -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 )

Expand Down Expand Up @@ -203,6 +211,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")
Expand Down
126 changes: 126 additions & 0 deletions client/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
#include <logging.h>
#include <user.h>

#ifdef USE_KEYCHAIN
#include <qt5keychain/keychain.h>
#endif

#include <QtCore/QTimer>
#include <QtCore/QDebug>
#include <QtCore/QStandardPaths>
Expand Down Expand Up @@ -407,6 +411,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))
Expand All @@ -424,8 +437,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) };
Expand Down Expand Up @@ -468,6 +543,39 @@ 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());
const auto button = QMessageBox::warning(this,
tr("Couldn't save access token"),
tr("Quaternion couldn't save the access token to keychain."
" 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;
}
#endif

void MainWindow::enableDebug()
{
chatRoomWidget->enableDebug();
Expand Down Expand Up @@ -791,6 +899,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();
}

Expand Down
6 changes: 6 additions & 0 deletions client/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

0 comments on commit d1c79f1

Please sign in to comment.