1 /*
2 SPDX-FileCopyrightText: 2018 Krzysztof Nowicki <krissn@op.pl>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "ewsoauth.h"
8
9 #include <QUrlQuery>
10 #ifdef EWSOAUTH_UNITTEST
11 #include "ewsoauth_ut_mock.h"
12 using namespace Mock;
13 #else
14 #include "ewspkeyauthjob.h"
15 #include <QAbstractOAuthReplyHandler>
16 #include <QDialog>
17 #include <QHBoxLayout>
18 #include <QIcon>
19 #include <QJsonDocument>
20 #include <QJsonObject>
21 #include <QNetworkReply>
22 #include <QOAuth2AuthorizationCodeFlow>
23 #include <QPointer>
24 #include <QWebEngineProfile>
25 #include <QWebEngineUrlRequestInterceptor>
26 #include <QWebEngineUrlRequestJob>
27 #include <QWebEngineUrlSchemeHandler>
28 #include <QWebEngineView>
29 #endif
30 #include "ewsclient_debug.h"
31 #include <KLocalizedString>
32 #include <QJsonDocument>
33 #include <QTcpServer>
34 #include <QTcpSocket>
35
36 static const auto o365AuthorizationUrl = QUrl(QStringLiteral("https://login.microsoftonline.com/common/oauth2/authorize"));
37 static const auto o365AccessTokenUrl = QUrl(QStringLiteral("https://login.microsoftonline.com/common/oauth2/token"));
38 static const auto o365FakeUserAgent =
39 QStringLiteral("Mozilla/5.0 (Linux; Android 7.0; SM-G930V Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36");
40 static const auto o365Resource = QStringLiteral("https%3A%2F%2Foutlook.office365.com%2F");
41
42 static const auto pkeyAuthSuffix = QStringLiteral(" PKeyAuth/1.0");
43 static const auto pkeyRedirectUri = QStringLiteral("urn:http-auth:PKeyAuth");
44 static const QString pkeyPasswordMapKey = QStringLiteral("pkey-password");
45
46 static const QString accessTokenMapKey = QStringLiteral("access-token");
47 static const QString refreshTokenMapKey = QStringLiteral("refresh-token");
48
49 class EwsOAuthUrlSchemeHandler final : public QWebEngineUrlSchemeHandler
50 {
51 Q_OBJECT
52 public:
EwsOAuthUrlSchemeHandler(QObject * parent=nullptr)53 EwsOAuthUrlSchemeHandler(QObject *parent = nullptr)
54 : QWebEngineUrlSchemeHandler(parent)
55 {
56 }
57
58 ~EwsOAuthUrlSchemeHandler() override = default;
59 void requestStarted(QWebEngineUrlRequestJob *request) override;
60 Q_SIGNALS:
61 void returnUriReceived(const QUrl &url);
62 };
63
64 class EwsOAuthReplyHandler final : public QAbstractOAuthReplyHandler
65 {
66 Q_OBJECT
67 public:
EwsOAuthReplyHandler(QObject * parent,const QString & returnUri)68 EwsOAuthReplyHandler(QObject *parent, const QString &returnUri)
69 : QAbstractOAuthReplyHandler(parent)
70 , mReturnUri(returnUri)
71 {
72 }
73
74 ~EwsOAuthReplyHandler() override = default;
75
callback() const76 QString callback() const override
77 {
78 return mReturnUri;
79 }
80
81 void networkReplyFinished(QNetworkReply *reply) override;
82 Q_SIGNALS:
83 void replyError(const QString &error);
84
85 private:
86 const QString mReturnUri;
87 };
88
89 class EwsOAuthRequestInterceptor final : public QWebEngineUrlRequestInterceptor
90 {
91 Q_OBJECT
92 public:
93 EwsOAuthRequestInterceptor(QObject *parent, const QString &redirectUri, const QString &clientId);
94 ~EwsOAuthRequestInterceptor() override = default;
95
96 void interceptRequest(QWebEngineUrlRequestInfo &info) override;
97 public Q_SLOTS:
98 void setPKeyAuthInputArguments(const QString &pkeyCertFile, const QString &pkeyKeyFile, const QString &pkeyPassword);
99 Q_SIGNALS:
100 void redirectUriIntercepted(const QUrl &url);
101
102 private:
103 const QString mRedirectUri;
104 const QString mClientId;
105 QString mPKeyCertFile;
106 QString mPKeyKeyFile;
107 QString mPKeyPassword;
108 QString mPKeyAuthResponse;
109 QString mPKeyAuthSubmitUrl;
110 QTcpServer mRedirectServer;
111 };
112
113 class EwsOAuthPrivate final : public QObject
114 {
115 Q_OBJECT
116 public:
117 EwsOAuthPrivate(EwsOAuth *parent, const QString &email, const QString &appId, const QString &redirectUri);
118 ~EwsOAuthPrivate() override = default;
119
120 bool authenticate(bool interactive);
121
122 void modifyParametersFunction(QAbstractOAuth::Stage stage, QVariantMap *parameters);
123 void authorizeWithBrowser(const QUrl &url);
124 void redirectUriIntercepted(const QUrl &url);
125 void granted();
126 void error(const QString &error, const QString &errorDescription, const QUrl &uri);
127 QVariantMap queryToVarmap(const QUrl &url);
128 void pkeyAuthResult(KJob *job);
129
130 QWebEngineView mWebView;
131 QWebEngineProfile mWebProfile;
132 QWebEnginePage mWebPage;
133 QOAuth2AuthorizationCodeFlow mOAuth2;
134 EwsOAuthReplyHandler mReplyHandler;
135 EwsOAuthRequestInterceptor mRequestInterceptor;
136 EwsOAuthUrlSchemeHandler mSchemeHandler;
137 QString mToken;
138 const QString mEmail;
139 const QString mRedirectUri;
140 bool mAuthenticated;
141 QPointer<QDialog> mWebDialog;
142 QString mPKeyPassword;
143
144 EwsOAuth *q_ptr = nullptr;
145 Q_DECLARE_PUBLIC(EwsOAuth)
146 };
147
requestStarted(QWebEngineUrlRequestJob * request)148 void EwsOAuthUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *request)
149 {
150 Q_EMIT returnUriReceived(request->requestUrl());
151 }
152
networkReplyFinished(QNetworkReply * reply)153 void EwsOAuthReplyHandler::networkReplyFinished(QNetworkReply *reply)
154 {
155 if (reply->error() != QNetworkReply::NoError) {
156 Q_EMIT replyError(reply->errorString());
157 return;
158 } else if (reply->header(QNetworkRequest::ContentTypeHeader).isNull()) {
159 Q_EMIT replyError(QStringLiteral("Empty or no Content-type header"));
160 return;
161 }
162 const auto cth = reply->header(QNetworkRequest::ContentTypeHeader);
163 const auto ct = cth.isNull() ? QStringLiteral("text/html") : cth.toString();
164 const auto data = reply->readAll();
165 if (data.isEmpty()) {
166 Q_EMIT replyError(QStringLiteral("No data received"));
167 return;
168 }
169 Q_EMIT replyDataReceived(data);
170 QVariantMap tokens;
171 if (ct.startsWith(QLatin1String("text/html")) || ct.startsWith(QLatin1String("application/x-www-form-urlencoded"))) {
172 QUrlQuery q(QString::fromUtf8(data));
173 const auto items = q.queryItems(QUrl::FullyDecoded);
174 for (const auto &it : items) {
175 tokens.insert(it.first, it.second);
176 }
177 } else if (ct.startsWith(QLatin1String("application/json")) || ct.startsWith(QLatin1String("text/javascript"))) {
178 const auto document = QJsonDocument::fromJson(data);
179 if (!document.isObject()) {
180 Q_EMIT replyError(QStringLiteral("Invalid JSON data received"));
181 return;
182 }
183 const auto object = document.object();
184 if (object.isEmpty()) {
185 Q_EMIT replyError(QStringLiteral("Empty JSON data received"));
186 return;
187 }
188 tokens = object.toVariantMap();
189 } else {
190 Q_EMIT replyError(QStringLiteral("Unknown content type"));
191 return;
192 }
193
194 const auto error = tokens.value(QStringLiteral("error"));
195 if (error.isValid()) {
196 Q_EMIT replyError(QStringLiteral("Received error response: ") + error.toString());
197 return;
198 }
199 const auto accessToken = tokens.value(QStringLiteral("access_token"));
200 if (!accessToken.isValid() || accessToken.toString().isEmpty()) {
201 Q_EMIT replyError(QStringLiteral("Received empty or no access token"));
202 return;
203 }
204
205 Q_EMIT tokensReceived(tokens);
206 }
207
EwsOAuthRequestInterceptor(QObject * parent,const QString & redirectUri,const QString & clientId)208 EwsOAuthRequestInterceptor::EwsOAuthRequestInterceptor(QObject *parent, const QString &redirectUri, const QString &clientId)
209 : QWebEngineUrlRequestInterceptor(parent)
210 , mRedirectUri(redirectUri)
211 , mClientId(clientId)
212 {
213 /* Workaround for QTBUG-88861 - start a trivial HTTP server to serve the redirect.
214 * The redirection must be done using JavaScript as HTTP-protocol redirections (301, 302)
215 * do not cause QWebEngineUrlRequestInterceptor::interceptRequest() to fire. */
216 connect(&mRedirectServer, &QTcpServer::newConnection, this, [this]() {
217 const auto socket = mRedirectServer.nextPendingConnection();
218 if (socket) {
219 connect(socket, &QIODevice::readyRead, this, [this, socket]() {
220 const auto response = QStringLiteral(
221 "HTTP/1.1 200 OK\n\n<!DOCTYPE html>\n<html><body><p>You will be redirected "
222 "shortly.</p><script>window.location.href=\"%1\";</script></body></html>\n")
223 .arg(mPKeyAuthSubmitUrl);
224 socket->write(response.toLocal8Bit());
225 });
226 connect(socket, &QIODevice::bytesWritten, this, [socket]() {
227 socket->deleteLater();
228 });
229 }
230 });
231 mRedirectServer.listen(QHostAddress::LocalHost);
232 }
233
interceptRequest(QWebEngineUrlRequestInfo & info)234 void EwsOAuthRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
235 {
236 const auto url = info.requestUrl();
237
238 qCDebugNC(EWSCLI_LOG) << QStringLiteral("Intercepted browser navigation to ") << url;
239
240 if (url.toString(QUrl::RemoveQuery) == pkeyRedirectUri) {
241 qCDebugNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth URI");
242
243 auto pkeyAuthJob = new EwsPKeyAuthJob(url, mPKeyCertFile, mPKeyKeyFile, mPKeyPassword, this);
244 mPKeyAuthResponse = pkeyAuthJob->getAuthHeader();
245 QUrlQuery query(url.query());
246 if (!mPKeyAuthResponse.isEmpty() && query.hasQueryItem(QStringLiteral("SubmitUrl"))) {
247 mPKeyAuthSubmitUrl = query.queryItemValue(QStringLiteral("SubmitUrl"), QUrl::FullyDecoded);
248 /* Workaround for QTBUG-88861
249 * When the PKey authentication starts, the server issues a request for a "special" PKey URL
250 * containing the challenge arguments and expects that a response is composed and the browser
251 * then redirected to the URL found in the SubmitUrl argument with the response passed using
252 * the HTTP Authorization header.
253 * Unfortunately the Qt WebEngine request interception mechanism will ignore custom HTTP headers
254 * when issuing a redirect.
255 * To work around that the EWS Resource launches a minimalistic HTTP server to serve a
256 * simple webpage with redirection. This way the redirection happens externally to the
257 * Qt Web Engine and the submit URL can be captured by the request interceptor again, this time
258 * only to add the missing Authorization header. */
259 qCDebugNC(EWSCLI_LOG) << QStringLiteral("Redirecting to PKey SubmitUrl via QTBUG-88861 workaround");
260 info.redirect(QUrl(QStringLiteral("http://localhost:%1/").arg(mRedirectServer.serverPort())));
261 } else {
262 qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to retrieve PKey authorization header");
263 }
264 } else if (url.toString(QUrl::RemoveQuery) == mPKeyAuthSubmitUrl) {
265 info.setHttpHeader(QByteArray("Authorization"), mPKeyAuthResponse.toLocal8Bit());
266 } else if (url.toString(QUrl::RemoveQuery) == mRedirectUri) {
267 qCDebug(EWSCLI_LOG) << QStringLiteral("Found redirect URI - blocking request");
268
269 Q_EMIT redirectUriIntercepted(url);
270 info.block(true);
271 }
272 }
273
setPKeyAuthInputArguments(const QString & pkeyCertFile,const QString & pkeyKeyFile,const QString & pkeyPassword)274 void EwsOAuthRequestInterceptor::setPKeyAuthInputArguments(const QString &pkeyCertFile, const QString &pkeyKeyFile, const QString &pkeyPassword)
275 {
276 mPKeyCertFile = pkeyCertFile;
277 mPKeyKeyFile = pkeyKeyFile;
278 mPKeyPassword = pkeyPassword;
279 }
280
EwsOAuthPrivate(EwsOAuth * parent,const QString & email,const QString & appId,const QString & redirectUri)281 EwsOAuthPrivate::EwsOAuthPrivate(EwsOAuth *parent, const QString &email, const QString &appId, const QString &redirectUri)
282 : QObject(nullptr)
283 , mWebView(nullptr)
284 , mWebProfile()
285 , mWebPage(&mWebProfile)
286 , mReplyHandler(this, redirectUri)
287 , mRequestInterceptor(this, redirectUri, appId)
288 , mEmail(email)
289 , mRedirectUri(redirectUri)
290 , mAuthenticated(false)
291 , q_ptr(parent)
292 {
293 mOAuth2.setReplyHandler(&mReplyHandler);
294 mOAuth2.setAuthorizationUrl(o365AuthorizationUrl);
295 mOAuth2.setAccessTokenUrl(o365AccessTokenUrl);
296 mOAuth2.setClientIdentifier(appId);
297 mWebProfile.setUrlRequestInterceptor(&mRequestInterceptor);
298 mWebProfile.installUrlSchemeHandler("urn", &mSchemeHandler);
299
300 mWebView.setPage(&mWebPage);
301
302 mOAuth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap *parameters) {
303 modifyParametersFunction(stage, parameters);
304 });
305 connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &EwsOAuthPrivate::authorizeWithBrowser);
306 connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::granted, this, &EwsOAuthPrivate::granted);
307 connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::error, this, &EwsOAuthPrivate::error);
308 connect(&mRequestInterceptor, &EwsOAuthRequestInterceptor::redirectUriIntercepted, this, &EwsOAuthPrivate::redirectUriIntercepted, Qt::QueuedConnection);
309 connect(&mReplyHandler, &EwsOAuthReplyHandler::replyError, this, [this](const QString &err) {
310 error(QStringLiteral("Network reply error"), err, QUrl());
311 });
312 }
313
authenticate(bool interactive)314 bool EwsOAuthPrivate::authenticate(bool interactive)
315 {
316 // Q_Q(EwsOAuth);
317
318 qCInfoNC(EWSCLI_LOG) << QStringLiteral("Starting OAuth2 authentication");
319
320 if (!mOAuth2.refreshToken().isEmpty()) {
321 mOAuth2.refreshAccessToken();
322 return true;
323 } else if (interactive) {
324 mOAuth2.grant();
325 return true;
326 } else {
327 return false;
328 }
329 }
330
modifyParametersFunction(QAbstractOAuth::Stage stage,QVariantMap * parameters)331 void EwsOAuthPrivate::modifyParametersFunction(QAbstractOAuth::Stage stage, QVariantMap *parameters)
332 {
333 switch (stage) {
334 case QAbstractOAuth::Stage::RequestingAccessToken:
335 parameters->insert(QStringLiteral("resource"), o365Resource);
336 break;
337 case QAbstractOAuth::Stage::RequestingAuthorization:
338 parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
339 parameters->insert(QStringLiteral("login_hint"), mEmail);
340 parameters->insert(QStringLiteral("resource"), o365Resource);
341 break;
342 default:
343 break;
344 }
345 }
346
authorizeWithBrowser(const QUrl & url)347 void EwsOAuthPrivate::authorizeWithBrowser(const QUrl &url)
348 {
349 Q_Q(EwsOAuth);
350
351 qCInfoNC(EWSCLI_LOG) << QStringLiteral("Launching browser for authentication");
352
353 /* Bad bad Microsoft...
354 * When Conditional Access is enabled on the server the OAuth2 authentication server only supports Windows,
355 * MacOSX, Android and iOS. No option to include Linux. Support (i.e. guarantee that it works)
356 * is one thing, but blocking unsupported browsers completely is just wrong.
357 * Fortunately enough this can be worked around by faking the user agent to something "supported".
358 */
359 auto userAgent = o365FakeUserAgent;
360 if (!q->mPKeyCertFile.isNull() && !q->mPKeyKeyFile.isNull()) {
361 qCInfoNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth certificates");
362 userAgent += pkeyAuthSuffix;
363 } else {
364 qCInfoNC(EWSCLI_LOG) << QStringLiteral("PKeyAuth certificates not found");
365 }
366 mWebProfile.setHttpUserAgent(userAgent);
367
368 mRequestInterceptor.setPKeyAuthInputArguments(q->mPKeyCertFile, q->mPKeyKeyFile, mPKeyPassword);
369
370 mWebDialog = new QDialog(q->mAuthParentWidget);
371 mWebDialog->setObjectName(QStringLiteral("Akonadi EWS Resource - Authentication"));
372 mWebDialog->setWindowIcon(QIcon(QStringLiteral("akonadi-ews")));
373 mWebDialog->resize(400, 500);
374 auto layout = new QHBoxLayout(mWebDialog);
375 layout->setContentsMargins({});
376 layout->addWidget(&mWebView);
377 mWebView.show();
378
379 connect(mWebDialog.data(), &QDialog::rejected, this, [this]() {
380 error(QStringLiteral("User cancellation"), QStringLiteral("The authentication browser was closed"), QUrl());
381 });
382
383 mWebView.load(url);
384 mWebDialog->show();
385 }
386
queryToVarmap(const QUrl & url)387 QVariantMap EwsOAuthPrivate::queryToVarmap(const QUrl &url)
388 {
389 QUrlQuery query(url);
390 QVariantMap varmap;
391 const auto items = query.queryItems();
392 for (const auto &item : items) {
393 varmap[item.first] = item.second;
394 }
395 return varmap;
396 }
397
redirectUriIntercepted(const QUrl & url)398 void EwsOAuthPrivate::redirectUriIntercepted(const QUrl &url)
399 {
400 qCDebugNC(EWSCLI_LOG) << QStringLiteral("Intercepted redirect URI from browser: ") << url;
401
402 mWebView.stop();
403 mWebDialog->hide();
404
405 Q_Q(EwsOAuth);
406 if (url.toString(QUrl::RemoveQuery) == pkeyRedirectUri) {
407 qCDebugNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth URI");
408
409 auto pkeyAuthJob = new EwsPKeyAuthJob(url, q->mPKeyCertFile, q->mPKeyKeyFile, mPKeyPassword, this);
410
411 connect(pkeyAuthJob, &KJob::result, this, &EwsOAuthPrivate::pkeyAuthResult);
412
413 pkeyAuthJob->start();
414
415 return;
416 }
417 Q_EMIT mOAuth2.authorizationCallbackReceived(queryToVarmap(url));
418 }
419
pkeyAuthResult(KJob * j)420 void EwsOAuthPrivate::pkeyAuthResult(KJob *j)
421 {
422 auto job = qobject_cast<EwsPKeyAuthJob *>(j);
423
424 qCDebugNC(EWSCLI_LOG) << QStringLiteral("PKeyAuth result: %1").arg(job->error());
425 QVariantMap varmap;
426 if (job->error() == 0) {
427 varmap = queryToVarmap(job->resultUri());
428 } else {
429 varmap[QStringLiteral("error")] = job->errorString();
430 }
431 Q_EMIT mOAuth2.authorizationCallbackReceived(varmap);
432 }
433
granted()434 void EwsOAuthPrivate::granted()
435 {
436 Q_Q(EwsOAuth);
437
438 qCInfoNC(EWSCLI_LOG) << QStringLiteral("Authentication succeeded");
439
440 mAuthenticated = true;
441
442 QMap<QString, QString> map;
443 map[accessTokenMapKey] = mOAuth2.token();
444 map[refreshTokenMapKey] = mOAuth2.refreshToken();
445 Q_EMIT q->setWalletMap(map);
446
447 Q_EMIT q->authSucceeded();
448 }
449
error(const QString & error,const QString & errorDescription,const QUrl & uri)450 void EwsOAuthPrivate::error(const QString &error, const QString &errorDescription, const QUrl &uri)
451 {
452 Q_Q(EwsOAuth);
453
454 Q_UNUSED(uri)
455
456 mAuthenticated = false;
457
458 mOAuth2.setRefreshToken(QString());
459 qCInfoNC(EWSCLI_LOG) << QStringLiteral("Authentication failed: ") << error << errorDescription;
460
461 Q_EMIT q->authFailed(error);
462 }
463
EwsOAuth(QObject * parent,const QString & email,const QString & appId,const QString & redirectUri)464 EwsOAuth::EwsOAuth(QObject *parent, const QString &email, const QString &appId, const QString &redirectUri)
465 : EwsAbstractAuth(parent)
466 , d_ptr(new EwsOAuthPrivate(this, email, appId, redirectUri))
467 {
468 }
469
~EwsOAuth()470 EwsOAuth::~EwsOAuth()
471 {
472 }
473
init()474 void EwsOAuth::init()
475 {
476 Q_EMIT requestWalletMap();
477 }
478
getAuthData(QString & username,QString & password,QStringList & customHeaders)479 bool EwsOAuth::getAuthData(QString &username, QString &password, QStringList &customHeaders)
480 {
481 Q_D(const EwsOAuth);
482
483 Q_UNUSED(username)
484 Q_UNUSED(password)
485
486 if (d->mAuthenticated) {
487 customHeaders.append(QStringLiteral("Authorization: Bearer ") + d->mOAuth2.token());
488 return true;
489 } else {
490 return false;
491 }
492 }
493
notifyRequestAuthFailed()494 void EwsOAuth::notifyRequestAuthFailed()
495 {
496 Q_D(EwsOAuth);
497
498 d->mOAuth2.setToken(QString());
499 d->mAuthenticated = false;
500
501 EwsAbstractAuth::notifyRequestAuthFailed();
502 }
503
authenticate(bool interactive)504 bool EwsOAuth::authenticate(bool interactive)
505 {
506 Q_D(EwsOAuth);
507
508 return d->authenticate(interactive);
509 }
510
reauthPrompt() const511 const QString &EwsOAuth::reauthPrompt() const
512 {
513 static const QString prompt =
514 xi18nc("@info",
515 "Microsoft Exchange credentials for the account <b>%1</b> are no longer valid. You need to authenticate in order to continue using it.",
516 QStringLiteral("%1"));
517 return prompt;
518 }
519
authFailedPrompt() const520 const QString &EwsOAuth::authFailedPrompt() const
521 {
522 static const QString prompt =
523 xi18nc("@info",
524 "Failed to obtain credentials for Microsoft Exchange account <b>%1</b>. Please update it in the account settings page.",
525 QStringLiteral("%1"));
526 return prompt;
527 }
528
walletPasswordRequestFinished(const QString & password)529 void EwsOAuth::walletPasswordRequestFinished(const QString &password)
530 {
531 Q_UNUSED(password)
532 }
533
walletMapRequestFinished(const QMap<QString,QString> & map)534 void EwsOAuth::walletMapRequestFinished(const QMap<QString, QString> &map)
535 {
536 Q_D(EwsOAuth);
537
538 if (map.contains(pkeyPasswordMapKey)) {
539 d->mPKeyPassword = map[pkeyPasswordMapKey];
540 }
541 if (map.contains(refreshTokenMapKey)) {
542 d->mOAuth2.setRefreshToken(map[refreshTokenMapKey]);
543 }
544 if (map.contains(accessTokenMapKey)) {
545 d->mOAuth2.setToken(map[accessTokenMapKey]);
546 d->mAuthenticated = true;
547 Q_EMIT authSucceeded();
548 } else {
549 Q_EMIT authFailed(QStringLiteral("Access token request failed"));
550 }
551 }
552
553 #include "ewsoauth.moc"
554