File indexing completed on 2024-12-22 04:56:59

0001 /*
0002     SPDX-FileCopyrightText: 2018 Krzysztof Nowicki <krissn@op.pl>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ewsoauth.h"
0008 
0009 #include <QUrlQuery>
0010 #ifdef EWSOAUTH_UNITTEST
0011 #include "ewsoauth_ut_mock.h"
0012 using namespace Mock;
0013 #else
0014 #include "ewspkeyauthjob.h"
0015 #include <QAbstractOAuthReplyHandler>
0016 #include <QDialog>
0017 #include <QHBoxLayout>
0018 #include <QIcon>
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 #include <QNetworkReply>
0022 #include <QOAuth2AuthorizationCodeFlow>
0023 #include <QPointer>
0024 #include <QWebEngineProfile>
0025 #include <QWebEngineUrlRequestInterceptor>
0026 #include <QWebEngineUrlRequestJob>
0027 #include <QWebEngineUrlSchemeHandler>
0028 #include <QWebEngineView>
0029 #endif
0030 #include "ewsclient_debug.h"
0031 #include <KLocalizedString>
0032 #include <QJsonDocument>
0033 #include <QTcpServer>
0034 #include <QTcpSocket>
0035 
0036 static const auto o365AuthorizationUrl = QUrl(QStringLiteral("https://login.microsoftonline.com/common/oauth2/authorize"));
0037 static const auto o365AccessTokenUrl = QUrl(QStringLiteral("https://login.microsoftonline.com/common/oauth2/token"));
0038 static const auto o365FakeUserAgent =
0039     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");
0040 static const auto o365Resource = QStringLiteral("https%3A%2F%2Foutlook.office365.com%2F");
0041 
0042 static const auto pkeyAuthSuffix = QStringLiteral(" PKeyAuth/1.0");
0043 static const auto pkeyRedirectUri = QStringLiteral("urn:http-auth:PKeyAuth");
0044 static const QString pkeyPasswordMapKey = QStringLiteral("pkey-password");
0045 
0046 static const QString accessTokenMapKey = QStringLiteral("access-token");
0047 static const QString refreshTokenMapKey = QStringLiteral("refresh-token");
0048 
0049 class EwsOAuthUrlSchemeHandler final : public QWebEngineUrlSchemeHandler
0050 {
0051     Q_OBJECT
0052 public:
0053     EwsOAuthUrlSchemeHandler(QObject *parent = nullptr)
0054         : QWebEngineUrlSchemeHandler(parent)
0055     {
0056     }
0057 
0058     ~EwsOAuthUrlSchemeHandler() override = default;
0059     void requestStarted(QWebEngineUrlRequestJob *request) override;
0060 Q_SIGNALS:
0061     void returnUriReceived(const QUrl &url);
0062 };
0063 
0064 class EwsOAuthReplyHandler final : public QAbstractOAuthReplyHandler
0065 {
0066     Q_OBJECT
0067 public:
0068     EwsOAuthReplyHandler(QObject *parent, const QString &returnUri)
0069         : QAbstractOAuthReplyHandler(parent)
0070         , mReturnUri(returnUri)
0071     {
0072     }
0073 
0074     ~EwsOAuthReplyHandler() override = default;
0075 
0076     [[nodiscard]] QString callback() const override
0077     {
0078         return mReturnUri;
0079     }
0080 
0081     void networkReplyFinished(QNetworkReply *reply) override;
0082 Q_SIGNALS:
0083     void replyError(const QString &error);
0084 
0085 private:
0086     const QString mReturnUri;
0087 };
0088 
0089 class EwsOAuthRequestInterceptor final : public QWebEngineUrlRequestInterceptor
0090 {
0091     Q_OBJECT
0092 public:
0093     EwsOAuthRequestInterceptor(QObject *parent, const QString &redirectUri, const QString &clientId);
0094     ~EwsOAuthRequestInterceptor() override = default;
0095 
0096     void interceptRequest(QWebEngineUrlRequestInfo &info) override;
0097 public Q_SLOTS:
0098     void setPKeyAuthInputArguments(const QString &pkeyCertFile, const QString &pkeyKeyFile, const QString &pkeyPassword);
0099 Q_SIGNALS:
0100     void redirectUriIntercepted(const QUrl &url);
0101 
0102 private:
0103     const QString mRedirectUri;
0104     const QString mClientId;
0105     QString mPKeyCertFile;
0106     QString mPKeyKeyFile;
0107     QString mPKeyPassword;
0108     QString mPKeyAuthResponse;
0109     QString mPKeyAuthSubmitUrl;
0110     QTcpServer mRedirectServer;
0111 };
0112 
0113 class EwsOAuthPrivate final : public QObject
0114 {
0115     Q_OBJECT
0116 public:
0117     EwsOAuthPrivate(EwsOAuth *parent, const QString &email, const QString &appId, const QString &redirectUri);
0118     ~EwsOAuthPrivate() override = default;
0119 
0120     bool authenticate(bool interactive);
0121     void modifyParametersFunction(QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant> *parameters);
0122     void authorizeWithBrowser(const QUrl &url);
0123     void redirectUriIntercepted(const QUrl &url);
0124     void granted();
0125     void error(const QString &error, const QString &errorDescription, const QUrl &uri);
0126     QVariantMap queryToVarmap(const QUrl &url);
0127     void pkeyAuthResult(KJob *job);
0128 
0129     QWebEngineView mWebView;
0130     QWebEngineProfile mWebProfile;
0131     QWebEnginePage mWebPage;
0132     QOAuth2AuthorizationCodeFlow mOAuth2;
0133     EwsOAuthReplyHandler mReplyHandler;
0134     EwsOAuthRequestInterceptor mRequestInterceptor;
0135     EwsOAuthUrlSchemeHandler mSchemeHandler;
0136     QString mToken;
0137     const QString mEmail;
0138     const QString mRedirectUri;
0139     bool mAuthenticated;
0140     QPointer<QDialog> mWebDialog;
0141     QString mPKeyPassword;
0142 
0143     EwsOAuth *q_ptr = nullptr;
0144     Q_DECLARE_PUBLIC(EwsOAuth)
0145 };
0146 
0147 void EwsOAuthUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *request)
0148 {
0149     Q_EMIT returnUriReceived(request->requestUrl());
0150 }
0151 
0152 void EwsOAuthReplyHandler::networkReplyFinished(QNetworkReply *reply)
0153 {
0154     if (reply->error() != QNetworkReply::NoError) {
0155         Q_EMIT replyError(reply->errorString());
0156         return;
0157     } else if (reply->header(QNetworkRequest::ContentTypeHeader).isNull()) {
0158         Q_EMIT replyError(QStringLiteral("Empty or no Content-type header"));
0159         return;
0160     }
0161     const auto cth = reply->header(QNetworkRequest::ContentTypeHeader);
0162     const auto ct = cth.isNull() ? QStringLiteral("text/html") : cth.toString();
0163     const auto data = reply->readAll();
0164     if (data.isEmpty()) {
0165         Q_EMIT replyError(QStringLiteral("No data received"));
0166         return;
0167     }
0168     Q_EMIT replyDataReceived(data);
0169     QVariantMap tokens;
0170     if (ct.startsWith(QLatin1StringView("text/html")) || ct.startsWith(QLatin1StringView("application/x-www-form-urlencoded"))) {
0171         QUrlQuery q(QString::fromUtf8(data));
0172         const auto items = q.queryItems(QUrl::FullyDecoded);
0173         for (const auto &it : items) {
0174             tokens.insert(it.first, it.second);
0175         }
0176     } else if (ct.startsWith(QLatin1StringView("application/json")) || ct.startsWith(QLatin1StringView("text/javascript"))) {
0177         const auto document = QJsonDocument::fromJson(data);
0178         if (!document.isObject()) {
0179             Q_EMIT replyError(QStringLiteral("Invalid JSON data received"));
0180             return;
0181         }
0182         const auto object = document.object();
0183         if (object.isEmpty()) {
0184             Q_EMIT replyError(QStringLiteral("Empty JSON data received"));
0185             return;
0186         }
0187         tokens = object.toVariantMap();
0188     } else {
0189         Q_EMIT replyError(QStringLiteral("Unknown content type"));
0190         return;
0191     }
0192 
0193     const auto error = tokens.value(QStringLiteral("error"));
0194     if (error.isValid()) {
0195         Q_EMIT replyError(QStringLiteral("Received error response: ") + error.toString());
0196         return;
0197     }
0198     const auto accessToken = tokens.value(QStringLiteral("access_token"));
0199     if (!accessToken.isValid() || accessToken.toString().isEmpty()) {
0200         Q_EMIT replyError(QStringLiteral("Received empty or no access token"));
0201         return;
0202     }
0203 
0204     Q_EMIT tokensReceived(tokens);
0205 }
0206 
0207 EwsOAuthRequestInterceptor::EwsOAuthRequestInterceptor(QObject *parent, const QString &redirectUri, const QString &clientId)
0208     : QWebEngineUrlRequestInterceptor(parent)
0209     , mRedirectUri(redirectUri)
0210     , mClientId(clientId)
0211 {
0212     /* Workaround for QTBUG-88861 - start a trivial HTTP server to serve the redirect.
0213      * The redirection must be done using JavaScript as HTTP-protocol redirections (301, 302)
0214      * do not cause QWebEngineUrlRequestInterceptor::interceptRequest() to fire. */
0215     connect(&mRedirectServer, &QTcpServer::newConnection, this, [this]() {
0216         const auto socket = mRedirectServer.nextPendingConnection();
0217         if (socket) {
0218             connect(socket, &QIODevice::readyRead, this, [this, socket]() {
0219                 const auto response = QStringLiteral(
0220                                           "HTTP/1.1 200 OK\n\n<!DOCTYPE html>\n<html><body><p>You will be redirected "
0221                                           "shortly.</p><script>window.location.href=\"%1\";</script></body></html>\n")
0222                                           .arg(mPKeyAuthSubmitUrl);
0223                 socket->write(response.toLocal8Bit());
0224             });
0225             connect(socket, &QIODevice::bytesWritten, this, [socket]() {
0226                 socket->deleteLater();
0227             });
0228         }
0229     });
0230     mRedirectServer.listen(QHostAddress::LocalHost);
0231 }
0232 
0233 void EwsOAuthRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
0234 {
0235     const auto url = info.requestUrl();
0236 
0237     qCDebugNC(EWSCLI_LOG) << QStringLiteral("Intercepted browser navigation to ") << url;
0238 
0239     if (url.toString(QUrl::RemoveQuery) == pkeyRedirectUri) {
0240         qCDebugNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth URI");
0241 
0242         auto pkeyAuthJob = new EwsPKeyAuthJob(url, mPKeyCertFile, mPKeyKeyFile, mPKeyPassword, this);
0243         mPKeyAuthResponse = pkeyAuthJob->getAuthHeader();
0244         QUrlQuery query(url.query());
0245         if (!mPKeyAuthResponse.isEmpty() && query.hasQueryItem(QStringLiteral("SubmitUrl"))) {
0246             mPKeyAuthSubmitUrl = query.queryItemValue(QStringLiteral("SubmitUrl"), QUrl::FullyDecoded);
0247             /* Workaround for QTBUG-88861
0248              * When the PKey authentication starts, the server issues a request for a "special" PKey URL
0249              * containing the challenge arguments and expects that a response is composed and the browser
0250              * then redirected to the URL found in the SubmitUrl argument with the response passed using
0251              * the HTTP Authorization header.
0252              * Unfortunately the Qt WebEngine request interception mechanism will ignore custom HTTP headers
0253              * when issuing a redirect.
0254              * To work around that the EWS Resource launches a minimalistic HTTP server to serve a
0255              * simple webpage with redirection. This way the redirection happens externally to the
0256              * Qt Web Engine and the submit URL can be captured by the request interceptor again, this time
0257              * only to add the missing Authorization header. */
0258             qCDebugNC(EWSCLI_LOG) << QStringLiteral("Redirecting to PKey SubmitUrl via QTBUG-88861 workaround");
0259             info.redirect(QUrl(QStringLiteral("http://localhost:%1/").arg(mRedirectServer.serverPort())));
0260         } else {
0261             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to retrieve PKey authorization header");
0262         }
0263     } else if (url.toString(QUrl::RemoveQuery) == mPKeyAuthSubmitUrl) {
0264         info.setHttpHeader(QByteArray("Authorization"), mPKeyAuthResponse.toLocal8Bit());
0265     } else if (url.toString(QUrl::RemoveQuery) == mRedirectUri) {
0266         qCDebug(EWSCLI_LOG) << QStringLiteral("Found redirect URI - blocking request");
0267 
0268         Q_EMIT redirectUriIntercepted(url);
0269         info.block(true);
0270     }
0271 }
0272 
0273 void EwsOAuthRequestInterceptor::setPKeyAuthInputArguments(const QString &pkeyCertFile, const QString &pkeyKeyFile, const QString &pkeyPassword)
0274 {
0275     mPKeyCertFile = pkeyCertFile;
0276     mPKeyKeyFile = pkeyKeyFile;
0277     mPKeyPassword = pkeyPassword;
0278 }
0279 
0280 EwsOAuthPrivate::EwsOAuthPrivate(EwsOAuth *parent, const QString &email, const QString &appId, const QString &redirectUri)
0281     : QObject(nullptr)
0282     , mWebView((QWidget *)nullptr)
0283     , mWebProfile()
0284     , mWebPage(&mWebProfile)
0285     , mReplyHandler(this, redirectUri)
0286     , mRequestInterceptor(this, redirectUri, appId)
0287     , mEmail(email)
0288     , mRedirectUri(redirectUri)
0289     , mAuthenticated(false)
0290     , q_ptr(parent)
0291 {
0292     mOAuth2.setReplyHandler(&mReplyHandler);
0293     mOAuth2.setAuthorizationUrl(o365AuthorizationUrl);
0294     mOAuth2.setAccessTokenUrl(o365AccessTokenUrl);
0295     mOAuth2.setClientIdentifier(appId);
0296     mWebProfile.setUrlRequestInterceptor(&mRequestInterceptor);
0297     mWebProfile.installUrlSchemeHandler("urn", &mSchemeHandler);
0298 
0299     mWebView.setPage(&mWebPage);
0300     mOAuth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant> *parameters) {
0301         modifyParametersFunction(stage, parameters);
0302     });
0303     connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &EwsOAuthPrivate::authorizeWithBrowser);
0304     connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::granted, this, &EwsOAuthPrivate::granted);
0305     connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::error, this, &EwsOAuthPrivate::error);
0306     connect(&mRequestInterceptor, &EwsOAuthRequestInterceptor::redirectUriIntercepted, this, &EwsOAuthPrivate::redirectUriIntercepted, Qt::QueuedConnection);
0307     connect(&mReplyHandler, &EwsOAuthReplyHandler::replyError, this, [this](const QString &err) {
0308         error(QStringLiteral("Network reply error"), err, QUrl());
0309     });
0310 }
0311 
0312 bool EwsOAuthPrivate::authenticate(bool interactive)
0313 {
0314     // Q_Q(EwsOAuth);
0315 
0316     qCInfoNC(EWSCLI_LOG) << QStringLiteral("Starting OAuth2 authentication");
0317 
0318     if (!mOAuth2.refreshToken().isEmpty()) {
0319         mOAuth2.refreshAccessToken();
0320         return true;
0321     } else if (interactive) {
0322         mOAuth2.grant();
0323         return true;
0324     } else {
0325         return false;
0326     }
0327 }
0328 
0329 void EwsOAuthPrivate::modifyParametersFunction(QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant> *parameters)
0330 {
0331     switch (stage) {
0332     case QAbstractOAuth::Stage::RequestingAccessToken:
0333         parameters->insert(QStringLiteral("resource"), o365Resource);
0334         break;
0335     case QAbstractOAuth::Stage::RequestingAuthorization:
0336         parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
0337         parameters->insert(QStringLiteral("login_hint"), mEmail);
0338         parameters->insert(QStringLiteral("resource"), o365Resource);
0339         break;
0340     default:
0341         break;
0342     }
0343 }
0344 
0345 void EwsOAuthPrivate::authorizeWithBrowser(const QUrl &url)
0346 {
0347     Q_Q(EwsOAuth);
0348 
0349     qCInfoNC(EWSCLI_LOG) << QStringLiteral("Launching browser for authentication");
0350 
0351     /* Bad bad Microsoft...
0352      * When Conditional Access is enabled on the server the OAuth2 authentication server only supports Windows,
0353      * MacOSX, Android and iOS. No option to include Linux. Support (i.e. guarantee that it works)
0354      * is one thing, but blocking unsupported browsers completely is just wrong.
0355      * Fortunately enough this can be worked around by faking the user agent to something "supported".
0356      */
0357     auto userAgent = o365FakeUserAgent;
0358     if (!q->mPKeyCertFile.isNull() && !q->mPKeyKeyFile.isNull()) {
0359         qCInfoNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth certificates");
0360         userAgent += pkeyAuthSuffix;
0361     } else {
0362         qCInfoNC(EWSCLI_LOG) << QStringLiteral("PKeyAuth certificates not found");
0363     }
0364     mWebProfile.setHttpUserAgent(userAgent);
0365 
0366     mRequestInterceptor.setPKeyAuthInputArguments(q->mPKeyCertFile, q->mPKeyKeyFile, mPKeyPassword);
0367 
0368     mWebDialog = new QDialog(q->mAuthParentWidget);
0369     mWebDialog->setObjectName(QLatin1StringView("Akonadi EWS Resource - Authentication"));
0370     mWebDialog->setWindowIcon(QIcon(QStringLiteral("akonadi-ews")));
0371     mWebDialog->resize(400, 500);
0372     auto layout = new QHBoxLayout(mWebDialog);
0373     layout->setContentsMargins({});
0374     layout->addWidget(&mWebView);
0375     mWebView.show();
0376 
0377     connect(mWebDialog.data(), &QDialog::rejected, this, [this]() {
0378         error(QStringLiteral("User cancellation"), QStringLiteral("The authentication browser was closed"), QUrl());
0379     });
0380 
0381     mWebView.load(url);
0382     mWebDialog->show();
0383 }
0384 
0385 QVariantMap EwsOAuthPrivate::queryToVarmap(const QUrl &url)
0386 {
0387     QUrlQuery query(url);
0388     QVariantMap varmap;
0389     const auto items = query.queryItems();
0390     for (const auto &item : items) {
0391         varmap[item.first] = item.second;
0392     }
0393     return varmap;
0394 }
0395 
0396 void EwsOAuthPrivate::redirectUriIntercepted(const QUrl &url)
0397 {
0398     qCDebugNC(EWSCLI_LOG) << QStringLiteral("Intercepted redirect URI from browser: ") << url;
0399 
0400     mWebView.stop();
0401     mWebDialog->hide();
0402 
0403     Q_Q(EwsOAuth);
0404     if (url.toString(QUrl::RemoveQuery) == pkeyRedirectUri) {
0405         qCDebugNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth URI");
0406 
0407         auto pkeyAuthJob = new EwsPKeyAuthJob(url, q->mPKeyCertFile, q->mPKeyKeyFile, mPKeyPassword, this);
0408 
0409         connect(pkeyAuthJob, &KJob::result, this, &EwsOAuthPrivate::pkeyAuthResult);
0410 
0411         pkeyAuthJob->start();
0412 
0413         return;
0414     }
0415     Q_EMIT mOAuth2.authorizationCallbackReceived(queryToVarmap(url));
0416 }
0417 
0418 void EwsOAuthPrivate::pkeyAuthResult(KJob *j)
0419 {
0420     auto job = qobject_cast<EwsPKeyAuthJob *>(j);
0421 
0422     qCDebugNC(EWSCLI_LOG) << QStringLiteral("PKeyAuth result: %1").arg(job->error());
0423     QVariantMap varmap;
0424     if (job->error() == 0) {
0425         varmap = queryToVarmap(job->resultUri());
0426     } else {
0427         varmap[QStringLiteral("error")] = job->errorString();
0428     }
0429     Q_EMIT mOAuth2.authorizationCallbackReceived(varmap);
0430 }
0431 
0432 void EwsOAuthPrivate::granted()
0433 {
0434     Q_Q(EwsOAuth);
0435 
0436     qCInfoNC(EWSCLI_LOG) << QStringLiteral("Authentication succeeded");
0437 
0438     mAuthenticated = true;
0439 
0440     QMap<QString, QString> map;
0441     map[accessTokenMapKey] = mOAuth2.token();
0442     map[refreshTokenMapKey] = mOAuth2.refreshToken();
0443     Q_EMIT q->setWalletMap(map);
0444 
0445     Q_EMIT q->authSucceeded();
0446 }
0447 
0448 void EwsOAuthPrivate::error(const QString &error, const QString &errorDescription, const QUrl &uri)
0449 {
0450     Q_Q(EwsOAuth);
0451 
0452     Q_UNUSED(uri)
0453 
0454     mAuthenticated = false;
0455 
0456     mOAuth2.setRefreshToken(QString());
0457     qCInfoNC(EWSCLI_LOG) << QStringLiteral("Authentication failed: ") << error << errorDescription;
0458 
0459     Q_EMIT q->authFailed(error);
0460 }
0461 
0462 EwsOAuth::EwsOAuth(QObject *parent, const QString &email, const QString &appId, const QString &redirectUri)
0463     : EwsAbstractAuth(parent)
0464     , d_ptr(new EwsOAuthPrivate(this, email, appId, redirectUri))
0465 {
0466 }
0467 
0468 EwsOAuth::~EwsOAuth()
0469 {
0470 }
0471 
0472 void EwsOAuth::init()
0473 {
0474     Q_EMIT requestWalletMap();
0475 }
0476 
0477 bool EwsOAuth::getAuthData(QString &username, QString &password, QStringList &customHeaders)
0478 {
0479     Q_D(const EwsOAuth);
0480 
0481     Q_UNUSED(username)
0482     Q_UNUSED(password)
0483 
0484     if (d->mAuthenticated) {
0485         customHeaders.append(QStringLiteral("Authorization: Bearer ") + d->mOAuth2.token());
0486         return true;
0487     } else {
0488         return false;
0489     }
0490 }
0491 
0492 void EwsOAuth::notifyRequestAuthFailed()
0493 {
0494     Q_D(EwsOAuth);
0495 
0496     d->mOAuth2.setToken(QString());
0497     d->mAuthenticated = false;
0498 
0499     EwsAbstractAuth::notifyRequestAuthFailed();
0500 }
0501 
0502 bool EwsOAuth::authenticate(bool interactive)
0503 {
0504     Q_D(EwsOAuth);
0505 
0506     return d->authenticate(interactive);
0507 }
0508 
0509 const QString &EwsOAuth::reauthPrompt() const
0510 {
0511     static const QString prompt =
0512         xi18nc("@info",
0513                "Microsoft Exchange credentials for the account <b>%1</b> are no longer valid. You need to authenticate in order to continue using it.",
0514                QStringLiteral("%1"));
0515     return prompt;
0516 }
0517 
0518 const QString &EwsOAuth::authFailedPrompt() const
0519 {
0520     static const QString prompt =
0521         xi18nc("@info",
0522                "Failed to obtain credentials for Microsoft Exchange account <b>%1</b>. Please update it in the account settings page.",
0523                QStringLiteral("%1"));
0524     return prompt;
0525 }
0526 
0527 void EwsOAuth::walletPasswordRequestFinished(const QString &password)
0528 {
0529     Q_UNUSED(password)
0530 }
0531 
0532 void EwsOAuth::walletMapRequestFinished(const QMap<QString, QString> &map)
0533 {
0534     Q_D(EwsOAuth);
0535 
0536     if (map.contains(pkeyPasswordMapKey)) {
0537         d->mPKeyPassword = map[pkeyPasswordMapKey];
0538     }
0539     if (map.contains(refreshTokenMapKey)) {
0540         d->mOAuth2.setRefreshToken(map[refreshTokenMapKey]);
0541     }
0542     if (map.contains(accessTokenMapKey)) {
0543         d->mOAuth2.setToken(map[accessTokenMapKey]);
0544         d->mAuthenticated = true;
0545         Q_EMIT authSucceeded();
0546     } else {
0547         Q_EMIT authFailed(QStringLiteral("Access token request failed"));
0548     }
0549 }
0550 
0551 #include "ewsoauth.moc"
0552 
0553 #include "moc_ewsoauth.cpp"