Kgapi

accountmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "accountmanager.h"
8#include "accountstorage_p.h"
9#include "authjob.h"
10#include "debug.h"
11
12#include <QDateTime>
13#include <QTimer>
14
15#include <functional>
16
17namespace KGAPI2
18{
19
20AccountManager *AccountManager::sInstance = nullptr;
21
22class AccountPromise::Private
23{
24public:
25 Private(AccountPromise *q)
26 : q(q)
27
28 {
29 }
30
31 void setError(const QString &error)
32 {
33 this->error = error;
34 emitFinished();
35 }
36
37 void setAccount(const AccountPtr &account)
38 {
39 this->account = account;
40 emitFinished();
41 }
42
43 void setRunning()
44 {
45 mRunning = true;
46 }
47
48 bool isRunning() const
49 {
50 return mRunning;
51 }
52
53 QString error;
54 AccountPtr account;
55
56private:
57 void emitFinished()
58 {
59 QTimer::singleShot(0, q, [this]() {
60 Q_EMIT q->finished(q);
61 q->deleteLater();
62 });
63 }
64
65 bool mRunning = false;
66 AccountPromise *const q;
67};
68
69class AccountManager::Private
70{
71public:
72 Private(AccountManager *q)
73 : q(q)
74 {
75 }
76
77 void updateAccount(AccountPromise *promise, const QString &apiKey, const QString &apiSecret, const AccountPtr &account, const QList<QUrl> &requestedScopes)
78 {
79 if (!requestedScopes.isEmpty()) {
80 auto currentScopes = account->scopes();
81 for (const auto &requestedScope : requestedScopes) {
82 if (!currentScopes.contains(requestedScope)) {
83 currentScopes.push_back(requestedScope);
84 }
85 }
86 if (currentScopes != account->scopes()) {
87 account->setScopes(currentScopes);
88 }
89 }
90 auto job = new AuthJob(account, apiKey, apiSecret);
91 job->setUsername(account->accountName());
92 connect(job, &AuthJob::finished, q, [this, job, apiKey, promise]() {
93 if (job->error() != KGAPI2::NoError) {
94 promise->d->setError(tr("Failed to authenticate additional scopes"));
95 return;
96 }
97
98 mStore->storeAccount(apiKey, job->account());
99 promise->d->setAccount(job->account());
100 });
101 }
102
103 void createAccount(AccountPromise *promise, const QString &apiKey, const QString &apiSecret, const QString &accountName, const QList<QUrl> &scopes)
104 {
105 const auto account = AccountPtr::create(accountName, QString{}, QString{}, scopes);
106 updateAccount(promise, apiKey, apiSecret, account, {});
107 }
108
109 bool compareScopes(const QList<QUrl> &currentScopes, const QList<QUrl> &requestedScopes) const
110 {
111 for (const auto &scope : std::as_const(requestedScopes)) {
112 if (!currentScopes.contains(scope)) {
113 return false;
114 }
115 }
116 return true;
117 }
118
119 void ensureStore(const std::function<void(bool)> &callback)
120 {
121 if (!mStore) {
122 mStore = AccountStorageFactory::instance()->create();
123 }
124 if (!mStore->opened()) {
125 mStore->open(callback);
126 } else {
127 callback(true);
128 }
129 }
130
131 AccountPromise *createPromise(const QString &apiKey, const QString &accountName)
132 {
133 const QString key = apiKey + accountName;
134 auto promise = mPendingPromises.value(key, nullptr);
135 if (!promise) {
136 promise = new AccountPromise(q);
137 QObject::connect(promise, &AccountPromise::finished, q, [key, this]() {
138 mPendingPromises.remove(key);
139 });
140 mPendingPromises.insert(key, promise);
141 }
142 return promise;
143 }
144
145public:
146 AccountStorage *mStore = nullptr;
147
148private:
149 QHash<QString, AccountPromise *> mPendingPromises;
150
151 AccountManager *const q;
152};
153
154}
155
156using namespace KGAPI2;
157
158AccountPromise::AccountPromise(QObject *parent)
159 : QObject(parent)
160 , d(new Private(this))
161{
162}
163
164AccountPromise::~AccountPromise()
165{
166}
167
168AccountPtr AccountPromise::account() const
169{
170 return d->account;
171}
172
173bool AccountPromise::hasError() const
174{
175 return !d->error.isNull();
176}
177
178QString AccountPromise::errorText() const
179{
180 return d->error;
181}
182
183AccountManager::AccountManager(QObject *parent)
184 : QObject(parent)
185 , d(new Private(this))
186{
187}
188
189AccountManager::~AccountManager()
190{
191}
192
193AccountManager *AccountManager::instance()
194{
195 if (!sInstance) {
196 sInstance = new AccountManager;
197 }
198 return sInstance;
199}
200
201AccountPromise *AccountManager::getAccount(const QString &apiKey, const QString &apiSecret, const QString &accountName, const QList<QUrl> &scopes)
202{
203 auto promise = d->createPromise(apiKey, accountName);
204 if (!promise->d->isRunning()) {
205 // Start the process asynchronously so that caller has a chance to connect
206 // to AccountPromise signals.
207 QTimer::singleShot(0, this, [this, promise, apiKey, apiSecret, accountName, scopes]() {
208 d->ensureStore([this, promise, apiKey, apiSecret, accountName, scopes](bool storeOpened) {
209 if (!storeOpened) {
210 promise->d->setError(tr("Failed to open account store"));
211 return;
212 }
213
214 const auto account = d->mStore->getAccount(apiKey, accountName);
215 if (!account) {
216 d->createAccount(promise, apiKey, apiSecret, accountName, scopes);
217 } else {
218 if (d->compareScopes(account->scopes(), scopes)) {
219 // Don't hand out obviously expired tokens
220 if (account->expireDateTime() <= QDateTime::currentDateTime()) {
221 d->updateAccount(promise, apiKey, apiSecret, account, scopes);
222 } else {
223 promise->d->setAccount(account);
224 }
225 } else {
226 // Since installed apps can't keep the API secret truly a secret
227 // incremental authorization is not allowed by Google so we need
228 // to request a completely new token from scratch.
229 account->setAccessToken({});
230 account->setRefreshToken({});
231 account->setExpireDateTime({});
232 d->updateAccount(promise, apiKey, apiSecret, account, scopes);
233 }
234 }
235 });
236 });
237 promise->d->setRunning();
238 }
239 return promise;
240}
241
242AccountPromise *AccountManager::refreshTokens(const QString &apiKey, const QString &apiSecret, const QString &accountName)
243{
244 auto promise = d->createPromise(apiKey, accountName);
245 if (!promise->d->isRunning()) {
246 QTimer::singleShot(0, this, [this, promise, apiKey, apiSecret, accountName]() {
247 d->ensureStore([this, apiKey, promise, apiSecret, accountName](bool storeOpened) {
248 if (!storeOpened) {
249 promise->d->setError(tr("Failed to open account store"));
250 return;
251 }
252
253 const auto account = d->mStore->getAccount(apiKey, accountName);
254 if (!account) {
255 promise->d->setAccount({});
256 } else {
257 d->updateAccount(promise, apiKey, apiSecret, account, {});
258 }
259 });
260 });
261 promise->d->setRunning();
262 }
263 return promise;
264}
265
266AccountPromise *AccountManager::findAccount(const QString &apiKey, const QString &accountName, const QList<QUrl> &scopes)
267{
268 auto promise = new AccountPromise(this);
269 QTimer::singleShot(0, this, [this, promise, apiKey, accountName, scopes]() {
270 d->ensureStore([this, promise, apiKey, accountName, scopes](bool storeOpened) {
271 if (!storeOpened) {
272 promise->d->setError(tr("Failed to open account store"));
273 return;
274 }
275
276 const auto account = d->mStore->getAccount(apiKey, accountName);
277 if (!account) {
278 promise->d->setAccount({});
279 } else {
280 const auto currentScopes = account->scopes();
281 if (scopes.isEmpty() || d->compareScopes(currentScopes, scopes)) {
282 promise->d->setAccount(account);
283 } else {
284 promise->d->setAccount({});
285 }
286 }
287 });
288 });
289 promise->d->setRunning();
290 return promise;
291}
292
293void AccountManager::removeScopes(const QString &apiKey, const QString &accountName, const QList<QUrl> &removedScopes)
294{
295 d->ensureStore([this, apiKey, accountName, removedScopes](bool storeOpened) {
296 if (!storeOpened) {
297 return;
298 }
299
300 const auto account = d->mStore->getAccount(apiKey, accountName);
301 if (!account) {
302 return;
303 }
304
305 for (const auto &scope : removedScopes) {
306 account->removeScope(scope);
307 }
308 if (account->scopes().isEmpty()) {
309 d->mStore->removeAccount(apiKey, account->accountName());
310 } else {
311 // Since installed apps can't keep the API secret truly a secret
312 // incremental authorization is not allowed by Google so we need
313 // to request a completely new token from scratch.
314 account->setAccessToken({});
315 account->setRefreshToken({});
316 account->setExpireDateTime({});
317 d->mStore->storeAccount(apiKey, account);
318 }
319 });
320}
321
322#include "moc_accountmanager.cpp"
AccountPromise is a result of asynchronous operations of AccountManager.
void finished(KGAPI2::AccountPromise *self)
The retrieval has finished and the Account can be retrieved.
A job to authenticate against Google and fetch tokens.
Definition authjob.h:34
void finished(KGAPI2::Job *job)
Emitted when job has finished.
A job to fetch a single map tile described by a StaticMapUrl.
Definition blog.h:16
@ NoError
LibKGAPI error - no error.
Definition types.h:178
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QDateTime currentDateTime()
bool contains(const AT &value) const const
bool isEmpty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QString tr(const char *sourceText, const char *disambiguation, int n)
QSharedPointer< T > create(Args &&... args)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:11:00 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.