File indexing completed on 2024-05-19 05:00:39

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0003  * SPDX-FileCopyrightText: 2016 Elvis Angelaccio <elvis.angelaccio@kde.org>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  */
0008 
0009 #include "keychainaccountmanager.h"
0010 #include "gdrivedebug.h"
0011 
0012 #include <QDataStream>
0013 #include <QEventLoop>
0014 #include <QIODevice>
0015 
0016 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0017 #include <qt5keychain/keychain.h>
0018 #else
0019 #include <qt6keychain/keychain.h>
0020 #endif
0021 
0022 #include <KGAPI/AuthJob>
0023 #include <KIO/Job> //for stat.h
0024 
0025 QString KeychainAccountManager::s_apiKey = QStringLiteral("836279663462-qq9rt1k4smgqhvt461r6l54vo9qm09bg.apps.googleusercontent.com");
0026 QString KeychainAccountManager::s_apiSecret = QStringLiteral("5-k19mrwsAud5a1r-qijtTJQ");
0027 
0028 QSet<QString> KeychainAccountManager::accounts()
0029 {
0030     if (m_accounts.isEmpty()) {
0031         auto job = new QKeychain::ReadPasswordJob(QStringLiteral("KIO GDrive"));
0032         job->setKey(QStringLiteral("gdrive-accounts"));
0033         runKeychainJob(job);
0034 
0035         auto data = job->binaryData();
0036         m_accounts = deserialize<QSet<QString>>(&data);
0037 
0038         qCDebug(GDRIVE) << "Fetched" << m_accounts.count() << "account(s) from keychain";
0039     }
0040 
0041     return m_accounts;
0042 }
0043 
0044 KGAPI2::AccountPtr KeychainAccountManager::account(const QString &accountName)
0045 {
0046     if (!accounts().contains(accountName)) {
0047         return KGAPI2::AccountPtr(new KGAPI2::Account());
0048     }
0049 
0050     const auto entry = readMap(accountName);
0051 
0052     const QStringList scopes = entry.value(QStringLiteral("scopes")).split(QLatin1Char(','), Qt::SkipEmptyParts);
0053     QList<QUrl> scopeUrls;
0054     for (const QString &scope : scopes) {
0055         scopeUrls << QUrl::fromUserInput(scope);
0056     }
0057 
0058     return KGAPI2::AccountPtr(
0059         new KGAPI2::Account(accountName, entry.value(QStringLiteral("accessToken")), entry.value(QStringLiteral("refreshToken")), scopeUrls));
0060 }
0061 
0062 KGAPI2::AccountPtr KeychainAccountManager::createAccount()
0063 {
0064     auto account = KGAPI2::AccountPtr(new KGAPI2::Account());
0065     account->addScope(QUrl(QStringLiteral("https://www.googleapis.com/auth/drive")));
0066     account->addScope(QUrl(QStringLiteral("https://www.googleapis.com/auth/drive.file")));
0067     account->addScope(QUrl(QStringLiteral("https://www.googleapis.com/auth/drive.metadata.readonly")));
0068     account->addScope(QUrl(QStringLiteral("https://www.googleapis.com/auth/drive.readonly")));
0069 
0070     KGAPI2::AuthJob *authJob = new KGAPI2::AuthJob(account, s_apiKey, s_apiSecret);
0071 
0072     QEventLoop eventLoop;
0073     QObject::connect(authJob, &KGAPI2::Job::finished, &eventLoop, &QEventLoop::quit);
0074     eventLoop.exec();
0075 
0076     account = authJob->account();
0077     authJob->deleteLater();
0078 
0079     if (!account->accountName().isEmpty()) {
0080         storeAccount(account);
0081     }
0082 
0083     return account;
0084 }
0085 
0086 void KeychainAccountManager::storeAccount(const KGAPI2::AccountPtr &account)
0087 {
0088     qCDebug(GDRIVE) << "Storing account" << account->accessToken();
0089 
0090     QMap<QString, QString> entry;
0091     entry[QStringLiteral("accessToken")] = account->accessToken();
0092     entry[QStringLiteral("refreshToken")] = account->refreshToken();
0093     QStringList scopes;
0094     const auto accountScopes = account->scopes();
0095     for (const QUrl &scope : accountScopes) {
0096         scopes << scope.toString();
0097     }
0098     entry[QStringLiteral("scopes")] = scopes.join(QLatin1Char(','));
0099 
0100     writeMap(account->accountName(), entry);
0101     storeAccountName(account->accountName());
0102 }
0103 
0104 KGAPI2::AccountPtr KeychainAccountManager::refreshAccount(const KGAPI2::AccountPtr &account)
0105 {
0106     KGAPI2::AuthJob *authJob = new KGAPI2::AuthJob(account, s_apiKey, s_apiSecret);
0107     QEventLoop eventLoop;
0108     QObject::connect(authJob, &KGAPI2::Job::finished, &eventLoop, &QEventLoop::quit);
0109     eventLoop.exec();
0110     if (authJob->error() != KGAPI2::OK && authJob->error() != KGAPI2::NoError) {
0111         return KGAPI2::AccountPtr();
0112     }
0113 
0114     const KGAPI2::AccountPtr newAccount = authJob->account();
0115     storeAccount(newAccount);
0116     return newAccount;
0117 }
0118 
0119 void KeychainAccountManager::removeAccountName(const QString &accountName)
0120 {
0121     auto accounts = this->accounts();
0122     accounts.remove(accountName);
0123 
0124     const auto data = serialize<QSet<QString>>(accounts);
0125 
0126     auto job = new QKeychain::WritePasswordJob(QStringLiteral("KIO GDrive"));
0127     job->setKey(QStringLiteral("gdrive-accounts"));
0128     job->setBinaryData(data);
0129     runKeychainJob(job);
0130 
0131     m_accounts = accounts;
0132 }
0133 
0134 void KeychainAccountManager::storeAccountName(const QString &accountName)
0135 {
0136     auto accounts = this->accounts();
0137     accounts.insert(accountName);
0138 
0139     const auto data = serialize<QSet<QString>>(accounts);
0140 
0141     auto job = new QKeychain::WritePasswordJob(QStringLiteral("KIO GDrive"));
0142     job->setKey(QStringLiteral("gdrive-accounts"));
0143     job->setBinaryData(data);
0144     runKeychainJob(job);
0145 
0146     m_accounts = accounts;
0147 }
0148 
0149 QMap<QString, QString> KeychainAccountManager::readMap(const QString &accountName)
0150 {
0151     auto job = new QKeychain::ReadPasswordJob(QStringLiteral("KIO GDrive"));
0152     job->setKey(accountName);
0153     runKeychainJob(job);
0154 
0155     if (job->error()) {
0156         return {};
0157     }
0158 
0159     auto data = job->binaryData();
0160     return deserialize<QMap<QString, QString>>(&data);
0161 }
0162 
0163 void KeychainAccountManager::writeMap(const QString &accountName, const QMap<QString, QString> &map)
0164 {
0165     const auto data = serialize<QMap<QString, QString>>(map);
0166 
0167     auto job = new QKeychain::WritePasswordJob(QStringLiteral("KIO GDrive"));
0168     job->setKey(accountName);
0169     job->setBinaryData(data);
0170     runKeychainJob(job);
0171 }
0172 
0173 void KeychainAccountManager::runKeychainJob(QKeychain::Job *job)
0174 {
0175     QObject::connect(job, &QKeychain::Job::finished, [](QKeychain::Job *job) {
0176         switch (job->error()) {
0177         case QKeychain::NoError:
0178             return;
0179         case QKeychain::EntryNotFound:
0180             qCDebug(GDRIVE) << "Keychain job could not find key" << job->key();
0181             return;
0182         case QKeychain::CouldNotDeleteEntry:
0183             qCDebug(GDRIVE) << "Keychain job could not delete key" << job->key();
0184             return;
0185         case QKeychain::AccessDenied:
0186         case QKeychain::AccessDeniedByUser:
0187             qCDebug(GDRIVE) << "Keychain job could not access the system keychain";
0188             break;
0189         default:
0190             qCDebug(GDRIVE) << "Keychain job failed:" << job->error() << "-" << job->errorString();
0191             return;
0192         }
0193     });
0194 
0195     QEventLoop eventLoop;
0196     QObject::connect(job, &QKeychain::Job::finished, &eventLoop, &QEventLoop::quit);
0197     job->start();
0198     eventLoop.exec();
0199 }
0200 
0201 void KeychainAccountManager::removeAccount(const QString &accountName)
0202 {
0203     auto job = new QKeychain::DeletePasswordJob(QStringLiteral("KIO GDrive"));
0204     job->setKey(accountName);
0205     runKeychainJob(job);
0206     removeAccountName(accountName);
0207 }
0208 
0209 template<typename T>
0210 QByteArray KeychainAccountManager::serialize(const T &object)
0211 {
0212     QByteArray data;
0213     QDataStream stream(&data, QIODevice::WriteOnly);
0214     stream.setVersion(QDataStream::Qt_5_0);
0215     stream << object;
0216 
0217     return data;
0218 }
0219 
0220 template<typename T>
0221 T KeychainAccountManager::deserialize(QByteArray *data)
0222 {
0223     if (!data) {
0224         return {};
0225     }
0226 
0227     QDataStream stream(data, QIODevice::ReadOnly);
0228     stream.setVersion(QDataStream::Qt_5_0);
0229 
0230     T object;
0231     stream >> object;
0232 
0233     return object;
0234 }