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"