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> &currentScopes, 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"