File indexing completed on 2024-05-12 05:22:14
0001 /* 0002 SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "accountmanager.h" 0008 #include "accountstorage_p.h" 0009 #include "authjob.h" 0010 #include "debug.h" 0011 0012 #include <QDateTime> 0013 #include <QTimer> 0014 0015 #include <functional> 0016 0017 namespace KGAPI2 0018 { 0019 0020 AccountManager *AccountManager::sInstance = nullptr; 0021 0022 class AccountPromise::Private 0023 { 0024 public: 0025 Private(AccountPromise *q) 0026 : q(q) 0027 0028 { 0029 } 0030 0031 void setError(const QString &error) 0032 { 0033 this->error = error; 0034 emitFinished(); 0035 } 0036 0037 void setAccount(const AccountPtr &account) 0038 { 0039 this->account = account; 0040 emitFinished(); 0041 } 0042 0043 void setRunning() 0044 { 0045 mRunning = true; 0046 } 0047 0048 bool isRunning() const 0049 { 0050 return mRunning; 0051 } 0052 0053 QString error; 0054 AccountPtr account; 0055 0056 private: 0057 void emitFinished() 0058 { 0059 QTimer::singleShot(0, q, [this]() { 0060 Q_EMIT q->finished(q); 0061 q->deleteLater(); 0062 }); 0063 } 0064 0065 bool mRunning = false; 0066 AccountPromise *const q; 0067 }; 0068 0069 class AccountManager::Private 0070 { 0071 public: 0072 Private(AccountManager *q) 0073 : q(q) 0074 { 0075 } 0076 0077 void updateAccount(AccountPromise *promise, const QString &apiKey, const QString &apiSecret, const AccountPtr &account, const QList<QUrl> &requestedScopes) 0078 { 0079 if (!requestedScopes.isEmpty()) { 0080 auto currentScopes = account->scopes(); 0081 for (const auto &requestedScope : requestedScopes) { 0082 if (!currentScopes.contains(requestedScope)) { 0083 currentScopes.push_back(requestedScope); 0084 } 0085 } 0086 if (currentScopes != account->scopes()) { 0087 account->setScopes(currentScopes); 0088 } 0089 } 0090 auto job = new AuthJob(account, apiKey, apiSecret); 0091 job->setUsername(account->accountName()); 0092 connect(job, &AuthJob::finished, q, [=]() { 0093 if (job->error() != KGAPI2::NoError) { 0094 promise->d->setError(tr("Failed to authenticate additional scopes")); 0095 return; 0096 } 0097 0098 mStore->storeAccount(apiKey, job->account()); 0099 promise->d->setAccount(job->account()); 0100 }); 0101 } 0102 0103 void createAccount(AccountPromise *promise, const QString &apiKey, const QString &apiSecret, const QString &accountName, const QList<QUrl> &scopes) 0104 { 0105 const auto account = AccountPtr::create(accountName, QString{}, QString{}, scopes); 0106 updateAccount(promise, apiKey, apiSecret, account, {}); 0107 } 0108 0109 bool compareScopes(const QList<QUrl> ¤tScopes, const QList<QUrl> &requestedScopes) const 0110 { 0111 for (const auto &scope : std::as_const(requestedScopes)) { 0112 if (!currentScopes.contains(scope)) { 0113 return false; 0114 } 0115 } 0116 return true; 0117 } 0118 0119 void ensureStore(const std::function<void(bool)> &callback) 0120 { 0121 if (!mStore) { 0122 mStore = AccountStorageFactory::instance()->create(); 0123 } 0124 if (!mStore->opened()) { 0125 mStore->open(callback); 0126 } else { 0127 callback(true); 0128 } 0129 } 0130 0131 AccountPromise *createPromise(const QString &apiKey, const QString &accountName) 0132 { 0133 const QString key = apiKey + accountName; 0134 auto promise = mPendingPromises.value(key, nullptr); 0135 if (!promise) { 0136 promise = new AccountPromise(q); 0137 QObject::connect(promise, &AccountPromise::finished, q, [key, this]() { 0138 mPendingPromises.remove(key); 0139 }); 0140 mPendingPromises.insert(key, promise); 0141 } 0142 return promise; 0143 } 0144 0145 public: 0146 AccountStorage *mStore = nullptr; 0147 0148 private: 0149 QHash<QString, AccountPromise *> mPendingPromises; 0150 0151 AccountManager *const q; 0152 }; 0153 0154 } 0155 0156 using namespace KGAPI2; 0157 0158 AccountPromise::AccountPromise(QObject *parent) 0159 : QObject(parent) 0160 , d(new Private(this)) 0161 { 0162 } 0163 0164 AccountPromise::~AccountPromise() 0165 { 0166 } 0167 0168 AccountPtr AccountPromise::account() const 0169 { 0170 return d->account; 0171 } 0172 0173 bool AccountPromise::hasError() const 0174 { 0175 return !d->error.isNull(); 0176 } 0177 0178 QString AccountPromise::errorText() const 0179 { 0180 return d->error; 0181 } 0182 0183 AccountManager::AccountManager(QObject *parent) 0184 : QObject(parent) 0185 , d(new Private(this)) 0186 { 0187 } 0188 0189 AccountManager::~AccountManager() 0190 { 0191 } 0192 0193 AccountManager *AccountManager::instance() 0194 { 0195 if (!sInstance) { 0196 sInstance = new AccountManager; 0197 } 0198 return sInstance; 0199 } 0200 0201 AccountPromise *AccountManager::getAccount(const QString &apiKey, const QString &apiSecret, const QString &accountName, const QList<QUrl> &scopes) 0202 { 0203 auto promise = d->createPromise(apiKey, accountName); 0204 if (!promise->d->isRunning()) { 0205 // Start the process asynchronously so that caller has a chance to connect 0206 // to AccountPromise signals. 0207 QTimer::singleShot(0, this, [=]() { 0208 d->ensureStore([=](bool storeOpened) { 0209 if (!storeOpened) { 0210 promise->d->setError(tr("Failed to open account store")); 0211 return; 0212 } 0213 0214 const auto account = d->mStore->getAccount(apiKey, accountName); 0215 if (!account) { 0216 d->createAccount(promise, apiKey, apiSecret, accountName, scopes); 0217 } else { 0218 if (d->compareScopes(account->scopes(), scopes)) { 0219 // Don't hand out obviously expired tokens 0220 if (account->expireDateTime() <= QDateTime::currentDateTime()) { 0221 d->updateAccount(promise, apiKey, apiSecret, account, scopes); 0222 } else { 0223 promise->d->setAccount(account); 0224 } 0225 } else { 0226 // Since installed apps can't keep the API secret truly a secret 0227 // incremental authorization is not allowed by Google so we need 0228 // to request a completely new token from scratch. 0229 account->setAccessToken({}); 0230 account->setRefreshToken({}); 0231 account->setExpireDateTime({}); 0232 d->updateAccount(promise, apiKey, apiSecret, account, scopes); 0233 } 0234 } 0235 }); 0236 }); 0237 promise->d->setRunning(); 0238 } 0239 return promise; 0240 } 0241 0242 AccountPromise *AccountManager::refreshTokens(const QString &apiKey, const QString &apiSecret, const QString &accountName) 0243 { 0244 auto promise = d->createPromise(apiKey, accountName); 0245 if (!promise->d->isRunning()) { 0246 QTimer::singleShot(0, this, [=]() { 0247 d->ensureStore([=](bool storeOpened) { 0248 if (!storeOpened) { 0249 promise->d->setError(tr("Failed to open account store")); 0250 return; 0251 } 0252 0253 const auto account = d->mStore->getAccount(apiKey, accountName); 0254 if (!account) { 0255 promise->d->setAccount({}); 0256 } else { 0257 d->updateAccount(promise, apiKey, apiSecret, account, {}); 0258 } 0259 }); 0260 }); 0261 promise->d->setRunning(); 0262 } 0263 return promise; 0264 } 0265 0266 AccountPromise *AccountManager::findAccount(const QString &apiKey, const QString &accountName, const QList<QUrl> &scopes) 0267 { 0268 auto promise = new AccountPromise(this); 0269 QTimer::singleShot(0, this, [=]() { 0270 d->ensureStore([=](bool storeOpened) { 0271 if (!storeOpened) { 0272 promise->d->setError(tr("Failed to open account store")); 0273 return; 0274 } 0275 0276 const auto account = d->mStore->getAccount(apiKey, accountName); 0277 if (!account) { 0278 promise->d->setAccount({}); 0279 } else { 0280 const auto currentScopes = account->scopes(); 0281 if (scopes.isEmpty() || d->compareScopes(currentScopes, scopes)) { 0282 promise->d->setAccount(account); 0283 } else { 0284 promise->d->setAccount({}); 0285 } 0286 } 0287 }); 0288 }); 0289 promise->d->setRunning(); 0290 return promise; 0291 } 0292 0293 void AccountManager::removeScopes(const QString &apiKey, const QString &accountName, const QList<QUrl> &removedScopes) 0294 { 0295 d->ensureStore([=](bool storeOpened) { 0296 if (!storeOpened) { 0297 return; 0298 } 0299 0300 const auto account = d->mStore->getAccount(apiKey, accountName); 0301 if (!account) { 0302 return; 0303 } 0304 0305 for (const auto &scope : removedScopes) { 0306 account->removeScope(scope); 0307 } 0308 if (account->scopes().isEmpty()) { 0309 d->mStore->removeAccount(apiKey, account->accountName()); 0310 } else { 0311 // Since installed apps can't keep the API secret truly a secret 0312 // incremental authorization is not allowed by Google so we need 0313 // to request a completely new token from scratch. 0314 account->setAccessToken({}); 0315 account->setRefreshToken({}); 0316 account->setExpireDateTime({}); 0317 d->mStore->storeAccount(apiKey, account); 0318 } 0319 }); 0320 } 0321 0322 #include "moc_accountmanager.cpp"