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 }