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