Commit 10e0cad5 authored by Elvis Angelaccio's avatar Elvis Angelaccio
Browse files

Port to qtkeychain

Accounts data is now stored through qtkeychain. This allows easier deployments
on platforms where KWallet is not available or desiderable.

qtkeychain's API is less expressive than KWallet's, so this port resulted in a bit more LOCs:

* it's not possible to store a QMap, we need to serialize it to QByteArray first.
* it's not possible to retrieve the list of stored entries (accounts), so we store an
  additional entry with the list of all stored accounts.
parent 1018af83
......@@ -27,11 +27,12 @@ include(ECMSetupVersion)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
I18n
KIO
Wallet)
KIO)
find_package(KF5GAPI ${KGAPI_MIN_VERSION} REQUIRED)
find_package(Qt5Keychain REQUIRED)
find_package(Qt5Test QUIET)
set_package_properties(Qt5Test PROPERTIES
TYPE OPTIONAL
......
include_directories(${CMAKE_SOURCE_DIR})
include_directories(${CMAKE_BINARY_DIR})
include_directories(${QTKEYCHAIN_INCLUDE_DIRS})
set(kio_gdrive_SRCS
kio_gdrive.cpp
......@@ -19,11 +20,11 @@ kcoreaddons_add_plugin(kio_gdrive
target_link_libraries(kio_gdrive
Qt5::Core
Qt5::Network
${QTKEYCHAIN_LIBRARIES}
KF5::GAPICore
KF5::GAPIDrive
KF5::KIOCore
KF5::KIOWidgets
KF5::I18n
KF5::Wallet)
KF5::I18n)
set_target_properties(kio_gdrive PROPERTIES OUTPUT_NAME "gdrive")
......@@ -20,56 +20,39 @@
#include "accountmanager.h"
#include "gdrivedebug.h"
#include <QDataStream>
#include <QEventLoop>
#include <qt5keychain/keychain.h>
#include <KIO/Job> //for stat.h
#include <KGAPI/AuthJob>
#include <KWallet>
QString AccountManager::s_apiKey = QStringLiteral("554041944266.apps.googleusercontent.com");
QString AccountManager::s_apiSecret = QStringLiteral("mdT1DjzohxN3npUUzkENT0gO");
AccountManager::AccountManager()
QSet<QString> AccountManager::accounts()
{
m_wallet = KWallet::Wallet::openWallet(
KWallet::Wallet::NetworkWallet(), 0,
KWallet::Wallet::Synchronous);
if (!m_wallet) {
qCWarning(GDRIVE) << "Failed to open KWallet";
} else {
if (!m_wallet->hasFolder(QLatin1String("GDrive"))) {
m_wallet->createFolder(QLatin1String("GDrive"));
}
if (m_accounts.isEmpty()) {
auto job = new QKeychain::ReadPasswordJob(QStringLiteral("KIO GDrive"));
job->setKey(QStringLiteral("gdrive-accounts"));
runKeychainJob(job);
m_wallet->setFolder(QLatin1String("GDrive"));
}
}
AccountManager::~AccountManager()
{
if (m_wallet) {
m_wallet->closeWallet(KWallet::Wallet::NetworkWallet(), false);
}
}
auto data = job->binaryData();
m_accounts = deserialize<QSet<QString>>(&data);
QStringList AccountManager::accounts()
{
if (!m_wallet) {
return QStringList();
qCDebug(GDRIVE) << "Fetched" << m_accounts.count() << "account(s) from keychain";
}
return m_wallet->entryList();
return m_accounts;
}
KGAPI2::AccountPtr AccountManager::account(const QString &accountName)
{
if (!m_wallet) {
return KGAPI2::AccountPtr();
}
KGAPI2::AccountPtr account;
if (accountName.isEmpty() || !m_wallet->entryList().contains(accountName)) {
if (accountName.isEmpty() || !accounts().contains(accountName)) {
account = KGAPI2::AccountPtr(new KGAPI2::Account(accountName));
account->addScope(QUrl("https://www.googleapis.com/auth/drive"));
account->addScope(QUrl("https://www.googleapis.com/auth/drive.file"));
......@@ -88,8 +71,7 @@ KGAPI2::AccountPtr AccountManager::account(const QString &accountName)
storeAccount(account);
} else {
QMap<QString, QString> entry;
m_wallet->readMap(accountName, entry);
const auto entry = readMap(accountName);
const QStringList scopes = entry.value(QStringLiteral("scopes")).split(QLatin1Char(','), QString::SkipEmptyParts);
QList<QUrl> scopeUrls;
......@@ -108,10 +90,6 @@ KGAPI2::AccountPtr AccountManager::account(const QString &accountName)
void AccountManager::storeAccount(const KGAPI2::AccountPtr &account)
{
if (!m_wallet) {
return;
}
qCDebug(GDRIVE) << "Storing account" << account->accessToken();
QMap<QString, QString> entry;
......@@ -123,7 +101,8 @@ void AccountManager::storeAccount(const KGAPI2::AccountPtr &account)
}
entry[ QStringLiteral("scopes") ] = scopes.join(QLatin1Char(','));
m_wallet->writeMap(account->accountName(), entry);
writeMap(account->accountName(), entry);
storeAccountName(account->accountName());
}
KGAPI2::AccountPtr AccountManager::refreshAccount(const KGAPI2::AccountPtr &account)
......@@ -156,11 +135,119 @@ KIO::UDSEntry AccountManager::accountToUDSEntry(const QString &accountNAme)
return entry;
}
void AccountManager::removeAccountName(const QString &accountName)
{
auto accounts = this->accounts();
accounts.remove(accountName);
const auto data = serialize<QSet<QString>>(accounts);
auto job = new QKeychain::WritePasswordJob(QStringLiteral("KIO GDrive"));
job->setKey(QStringLiteral("gdrive-accounts"));
job->setBinaryData(data);
runKeychainJob(job);
m_accounts = accounts;
}
void AccountManager::storeAccountName(const QString &accountName)
{
auto accounts = this->accounts();
accounts.insert(accountName);
const auto data = serialize<QSet<QString>>(accounts);
auto job = new QKeychain::WritePasswordJob(QStringLiteral("KIO GDrive"));
job->setKey(QStringLiteral("gdrive-accounts"));
job->setBinaryData(data);
runKeychainJob(job);
m_accounts = accounts;
}
QMap<QString, QString> AccountManager::readMap(const QString &accountName)
{
auto job = new QKeychain::ReadPasswordJob(QStringLiteral("KIO GDrive"));
job->setKey(accountName);
runKeychainJob(job);
if (job->error()) {
return {};
}
auto data = job->binaryData();
return deserialize<QMap<QString, QString>>(&data);
}
void AccountManager::writeMap(const QString &accountName, const QMap<QString, QString> &map)
{
const auto data = serialize<QMap<QString, QString>>(map);
auto job = new QKeychain::WritePasswordJob(QStringLiteral("KIO GDrive"));
job->setKey(accountName);
job->setBinaryData(data);
runKeychainJob(job);
}
void AccountManager::runKeychainJob(QKeychain::Job *job)
{
QObject::connect(job, &QKeychain::Job::finished, [](QKeychain::Job *job) {
switch (job->error()) {
case QKeychain::NoError:
return;
case QKeychain::EntryNotFound:
qCDebug(GDRIVE) << "Keychain job could not find key" << job->key();
return;
case QKeychain::CouldNotDeleteEntry:
qCDebug(GDRIVE) << "Keychain job could not delete key" << job->key();
return;
case QKeychain::AccessDenied:
case QKeychain::AccessDeniedByUser:
qCDebug(GDRIVE) << "Keychain job could not access the system keychain";
break;
default:
qCDebug(GDRIVE) << "Keychain job failed:" << job->error() << "-" << job->errorString();
return;
}
});
QEventLoop eventLoop;
QObject::connect(job, &QKeychain::Job::finished, &eventLoop, &QEventLoop::quit);
job->start();
eventLoop.exec();
}
void AccountManager::removeAccount(const QString &accountName)
{
if (!m_wallet) {
return;
auto job = new QKeychain::DeletePasswordJob(QStringLiteral("KIO GDrive"));
job->setKey(accountName);
runKeychainJob(job);
removeAccountName(accountName);
}
template <typename T>
QByteArray AccountManager::serialize(const T &object)
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream << object;
return data;
}
template <typename T>
T AccountManager::deserialize(QByteArray *data)
{
if (!data) {
return {};
}
m_wallet->removeEntry(accountName);
QDataStream stream(data, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
T object;
stream >> object;
return object;
}
......@@ -20,36 +20,44 @@
#ifndef ACCOUNTMANAGER_H
#define ACCOUNTMANAGER_H
#include <QStringList>
#include <QSet>
#include <KGAPI/Account>
#include <KIO/UDSEntry>
namespace KWallet
namespace QKeychain
{
class Wallet;
class Job;
}
class AccountManager
{
public:
AccountManager();
~AccountManager();
KGAPI2::AccountPtr account(const QString &accountName);
void storeAccount(const KGAPI2::AccountPtr &account);
KGAPI2::AccountPtr refreshAccount(const KGAPI2::AccountPtr &account);
void removeAccount(const QString &accountName);
QStringList accounts();
QSet<QString> accounts();
static KIO::UDSEntry accountToUDSEntry(const QString &accountName);
private:
template<typename T>
QByteArray serialize(const T& object);
template<typename T>
T deserialize(QByteArray *data);
// Store/remove account names in/from gdrive-accounts keychain entry.
void removeAccountName(const QString &accountName);
void storeAccountName(const QString &accountName);
KGAPI2::AccountPtr m_account;
QMap<QString, QString> readMap(const QString &accountName);
void writeMap(const QString &accountName, const QMap<QString, QString> &map);
void runKeychainJob(QKeychain::Job *job);
KWallet::Wallet *m_wallet;
QSet<QString> m_accounts;
static QString s_apiKey;
static QString s_apiSecret;
......
......@@ -45,7 +45,6 @@
#include <KIO/AccessManager>
#include <KIO/Job>
#include <KLocalizedString>
#include <KWallet>
#include <QNetworkRequest>
#include <QNetworkReply>
......@@ -233,7 +232,7 @@ void KIOGDrive::createAccount()
void KIOGDrive::listAccounts()
{
const QStringList accounts = m_accountManager.accounts();
const auto accounts = m_accountManager.accounts();
if (accounts.isEmpty()) {
createAccount();
return;
......@@ -843,7 +842,7 @@ void KIOGDrive::del(const QUrl &url, bool isfile)
const QString accountId = accountFromPath(url);
const QStringList components = pathComponents(url);
// If user tries to delete the account folder, remove the account from KWallet
// If user tries to delete the account folder, remove the account from the keychain
if (components.count() == 1) {
const KGAPI2::AccountPtr account = m_accountManager.account(accountId);
if (!account) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment