Kgapi

accountmanager.cpp
1 /*
2  Copyright (C) 2018 Daniel Vrátil <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Lesser General Public
6  License as published by the Free Software Foundation; either
7  version 2.1 of the License, or (at your option) version 3, or any
8  later version accepted by the membership of KDE e.V. (or its
9  successor approved by the membership of KDE e.V.), which shall
10  act as a proxy defined in Section 6 of version 3 of the license.
11 
12  This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU Lesser General Public License for more details.
16 
17  You should have received a copy of the GNU Lesser General Public
18  License along with this library. If not, see <https://www.gnu.org/licenses/>.
19 */
20 
21 #include "accountmanager.h"
22 #include "authjob.h"
23 #include "accountstorage_p.h"
24 #include "../debug.h"
25 
26 #include <QTimer>
27 #include <QDateTime>
28 
29 #include <functional>
30 
31 namespace KGAPI2 {
32 
33 AccountManager *AccountManager::sInstance = nullptr;
34 
35 class AccountPromise::Private
36 {
37 public:
38  Private(AccountPromise *q)
39  : q(q)
40 
41  {}
42 
43  void setError(const QString &error)
44  {
45  this->error = error;
46  emitFinished();
47  }
48 
49  void setAccount(const AccountPtr &account)
50  {
51  this->account = account;
52  emitFinished();
53  }
54 
55  void setRunning()
56  {
57  mRunning = true;
58  }
59 
60  bool isRunning() const
61  {
62  return mRunning;
63  }
64 
65 
66  QString error;
67  AccountPtr account;
68 private:
69  void emitFinished()
70  {
71  QTimer::singleShot(0, q, [this]() {
72  Q_EMIT q->finished(q);
73  q->deleteLater();
74  });
75  }
76 
77  bool mRunning = false;
78  AccountPromise * const q;
79 };
80 
81 class AccountManager::Private
82 {
83 public:
84  Private(AccountManager *q)
85  : q(q)
86  {}
87 
88  void updateAccount(AccountPromise *promise, const QString &apiKey, const QString &apiSecret,
89  const AccountPtr &account, const QList<QUrl> &requestedScopes)
90  {
91  if (!requestedScopes.isEmpty()) {
92  auto currentScopes = account->scopes();
93  for (const auto &requestedScope : requestedScopes) {
94  if (!currentScopes.contains(requestedScope)) {
95  currentScopes.push_back(requestedScope);
96  }
97  }
98  account->setScopes(currentScopes);
99  }
100  AuthJob *job = new AuthJob(account, apiKey, apiSecret);
101  job->setUsername(account->accountName());
103  [=]() {
104  if (job->error() != KGAPI2::NoError) {
105  promise->d->setError(tr("Failed to authenticate additional scopes"));
106  return;
107  }
108 
109  mStore->storeAccount(apiKey, job->account());
110  promise->d->setAccount(job->account());
111  });
112  }
113 
114  void createAccount(AccountPromise *promise, const QString &apiKey, const QString &apiSecret,
115  const QString &accountName, const QList<QUrl> &scopes)
116  {
117  const auto account = AccountPtr::create(accountName, QString{}, QString{}, scopes);
118  updateAccount(promise, apiKey, apiSecret, account, {});
119  }
120 
121  bool compareScopes(const QList<QUrl> &currentScopes, const QList<QUrl> &requestedScopes) const {
122  for (const auto &scope : qAsConst(requestedScopes)) {
123  if (!currentScopes.contains(scope)) {
124  return false;
125  }
126  }
127  return true;
128  }
129 
130  void ensureStore(const std::function<void(bool)> &callback)
131  {
132  if (!mStore) {
133  mStore = AccountStorageFactory::instance()->create();
134  }
135  if (!mStore->opened()) {
136  mStore->open(callback);
137  } else {
138  callback(true);
139  }
140  }
141 
142  AccountPromise *createPromise(const QString &apiKey, const QString &accountName)
143  {
144  const QString key = apiKey + accountName;
145  auto promise = mPendingPromises.value(key, nullptr);
146  if (!promise) {
147  promise = new AccountPromise(q);
149  q, [key, this]() {
150  mPendingPromises.remove(key);
151  });
152  mPendingPromises.insert(key, promise);
153  }
154  return promise;
155  }
156 public:
157  AccountStorage *mStore = nullptr;
158 
159 private:
160  QHash<QString, AccountPromise*> mPendingPromises;
161 
162  AccountManager * const q;
163 };
164 
165 }
166 
167 using namespace KGAPI2;
168 
169 
170 AccountPromise::AccountPromise(QObject *parent)
171  : QObject(parent)
172  , d(new Private(this))
173 {
174 }
175 
176 AccountPromise::~AccountPromise()
177 {
178 }
179 
180 AccountPtr AccountPromise::account() const
181 {
182  return d->account;
183 }
184 
185 bool AccountPromise::hasError() const
186 {
187  return !d->error.isNull();
188 }
189 
190 QString AccountPromise::errorText() const
191 {
192  return d->error;
193 }
194 
195 
196 AccountManager::AccountManager(QObject *parent)
197  : QObject(parent)
198  , d(new Private(this))
199 {
200 }
201 
202 AccountManager::~AccountManager()
203 {
204 }
205 
206 AccountManager *AccountManager::instance()
207 {
208  if (!sInstance) {
209  sInstance = new AccountManager;
210  }
211  return sInstance;
212 }
213 
214 AccountPromise *AccountManager::getAccount(const QString &apiKey, const QString &apiSecret,
215  const QString &accountName,
216  const QList<QUrl> &scopes)
217 {
218  auto promise = d->createPromise(apiKey, accountName);
219  if (!promise->d->isRunning()) {
220  // Start the process asynchronously so that caller has a chance to connect
221  // to AccountPromise signals.
222  QTimer::singleShot(0, this, [=]() {
223  d->ensureStore([=](bool storeOpened) {
224  if (!storeOpened) {
225  promise->d->setError(tr("Failed to open account store"));
226  return;
227  }
228 
229  const auto account = d->mStore->getAccount(apiKey, accountName);
230  if (!account) {
231  d->createAccount(promise, apiKey, apiSecret, accountName, scopes);
232  } else {
233  if (d->compareScopes(account->scopes(), scopes)) {
234  // Don't hand out obviously expired tokens
235  if (account->expireDateTime() <= QDateTime::currentDateTime()) {
236  d->updateAccount(promise, apiKey, apiSecret, account, {});
237  } else {
238  promise->d->setAccount(account);
239  }
240  } else {
241  // Since installed apps can't keep the API secret truly a secret
242  // incremental authorization is not allowed by Google so we need
243  // to request a completely new token from scratch.
244  account->setAccessToken({});
245  account->setRefreshToken({});
246  account->setExpireDateTime({});
247  d->updateAccount(promise, apiKey, apiSecret, account, scopes);
248  }
249  }
250  });
251  });
252  promise->d->setRunning();
253  }
254  return promise;
255 }
256 
257 AccountPromise *AccountManager::refreshTokens(const QString &apiKey, const QString &apiSecret,
258  const QString &accountName)
259 {
260  auto promise = d->createPromise(apiKey, accountName);
261  if (!promise->d->isRunning()) {
262  QTimer::singleShot(0, this, [=]() {
263  d->ensureStore([=](bool storeOpened) {
264  if (!storeOpened) {
265  promise->d->setError(tr("Failed to open account store"));
266  return;
267  }
268 
269  const auto account = d->mStore->getAccount(apiKey, accountName);
270  if (!account) {
271  promise->d->setAccount({});
272  } else {
273  d->updateAccount(promise, apiKey, apiSecret, account, {});
274  }
275  });
276  });
277  promise->d->setRunning();
278  }
279  return promise;
280 }
281 
282 
283 AccountPromise *AccountManager::findAccount(const QString &apiKey, const QString &accountName,
284  const QList<QUrl> &scopes)
285 {
286  auto promise = d->createPromise(apiKey, accountName);
287  if (!promise->d->isRunning()) {
288  QTimer::singleShot(0, this, [=]() {
289  d->ensureStore([=](bool storeOpened) {
290  if (!storeOpened) {
291  promise->d->setError(tr("Failed to open account store"));
292  return;
293  }
294 
295  const auto account = d->mStore->getAccount(apiKey, accountName);
296  if (!account) {
297  promise->d->setAccount({});
298  } else {
299  const auto currentScopes = account->scopes();
300  if (scopes.isEmpty() || d->compareScopes(currentScopes, scopes)) {
301  promise->d->setAccount(account);
302  } else {
303  promise->d->setAccount({});
304  }
305  }
306  });
307  });
308  promise->d->setRunning();
309  }
310  return promise;
311 }
312 
313 void AccountManager::removeScopes(const QString &apiKey, const QString &accountName,
314  const QList<QUrl> &removedScopes)
315 {
316  d->ensureStore([=](bool storeOpened) {
317  if (!storeOpened) {
318  return;
319  }
320 
321  const auto account = d->mStore->getAccount(apiKey, accountName);
322  if (!account) {
323  return;
324  }
325 
326  for (const auto &scope : removedScopes) {
327  account->removeScope(scope);
328  }
329  if (account->scopes().isEmpty()) {
330  d->mStore->removeAccount(apiKey, account->accountName());
331  } else {
332  // Since installed apps can't keep the API secret truly a secret
333  // incremental authorization is not allowed by Google so we need
334  // to request a completely new token from scratch.
335  account->setAccessToken({});
336  account->setRefreshToken({});
337  account->setExpireDateTime({});
338  d->mStore->storeAccount(apiKey, account);
339  }
340  });
341 }
QSharedPointer< T > create(Args &&...args)
LibKGAPI error - no error.
Definition: types.h:189
void finished(KGAPI2::Job *job)
Emitted when job has finished.
void setUsername(const QString &username)
Sets the username that will be used when authenticate is called.
Definition: authjob.cpp:165
QString tr(const char *sourceText, const char *disambiguation, int n)
KGAPI2::Error error() const
Error code.
Definition: job.cpp:392
bool isEmpty() const const
A job to fetch a single map tile described by a StaticMapUrl.
Definition: blog.h:30
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
AccountPromise is a result of asynchronous operations of AccountManager.
bool contains(const T &value) const const
QDateTime currentDateTime()
AccountPtr account() const
Returns reauthenticated account.
Definition: authjob.cpp:160
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
void destroyed(QObject *obj)
A job to authenticate against Google and fetch tokens.
Definition: authjob.h:47
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun May 24 2020 22:39:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.