diff --git a/resources/ews/ewsclient/auth/ewsoauth.cpp b/resources/ews/ewsclient/auth/ewsoauth.cpp index 720330984..c5bf6f754 100644 --- a/resources/ews/ewsclient/auth/ewsoauth.cpp +++ b/resources/ews/ewsclient/auth/ewsoauth.cpp @@ -1,498 +1,494 @@ /* Copyright (C) 2018 Krzysztof Nowicki This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ewsoauth.h" #include #ifdef EWSOAUTH_UNITTEST #include "ewsoauth_ut_mock.h" using namespace Mock; #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ewspkeyauthjob.h" #endif #include #include "ewsclient_debug.h" static const auto o365AuthorizationUrl = QUrl(QStringLiteral("https://login.microsoftonline.com/common/oauth2/authorize")); static const auto o365AccessTokenUrl = QUrl(QStringLiteral("https://login.microsoftonline.com/common/oauth2/token")); static const auto o365FakeUserAgent = 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"); static const auto o365Resource = QStringLiteral("https%3A%2F%2Foutlook.office365.com%2F"); static const auto pkeyAuthSuffix = QStringLiteral(" PKeyAuth/1.0"); static const auto pkeyRedirectUri = QStringLiteral("urn:http-auth:PKeyAuth"); static const QString pkeyPasswordMapKey = QStringLiteral("pkey-password"); static const QString accessTokenMapKey = QStringLiteral("access-token"); static const QString refreshTokenMapKey = QStringLiteral("refresh-token"); class EwsOAuthUrlSchemeHandler final : public QWebEngineUrlSchemeHandler { Q_OBJECT public: EwsOAuthUrlSchemeHandler(QObject *parent = nullptr) : QWebEngineUrlSchemeHandler(parent) { } ~EwsOAuthUrlSchemeHandler() override = default; void requestStarted(QWebEngineUrlRequestJob *request) override; Q_SIGNALS: void returnUriReceived(const QUrl &url); }; class EwsOAuthReplyHandler final : public QAbstractOAuthReplyHandler { Q_OBJECT public: EwsOAuthReplyHandler(QObject *parent, const QString &returnUri) : QAbstractOAuthReplyHandler(parent) , mReturnUri(returnUri) { } ~EwsOAuthReplyHandler() override = default; QString callback() const override { return mReturnUri; } void networkReplyFinished(QNetworkReply *reply) override; Q_SIGNALS: void replyError(const QString &error); private: const QString mReturnUri; }; class EwsOAuthRequestInterceptor final : public QWebEngineUrlRequestInterceptor { Q_OBJECT public: EwsOAuthRequestInterceptor(QObject *parent, const QString &redirectUri) : QWebEngineUrlRequestInterceptor(parent) , mRedirectUri(redirectUri) { } ~EwsOAuthRequestInterceptor() override = default; void interceptRequest(QWebEngineUrlRequestInfo &info) override; Q_SIGNALS: void redirectUriIntercepted(const QUrl &url); private: const QString mRedirectUri; }; class EwsOAuthPrivate final : public QObject { Q_OBJECT public: EwsOAuthPrivate(EwsOAuth *parent, const QString &email, const QString &appId, const QString &redirectUri); ~EwsOAuthPrivate() override = default; bool authenticate(bool interactive); void modifyParametersFunction(QAbstractOAuth::Stage stage, QVariantMap *parameters); void authorizeWithBrowser(const QUrl &url); void redirectUriIntercepted(const QUrl &url); void granted(); void error(const QString &error, const QString &errorDescription, const QUrl &uri); QVariantMap queryToVarmap(const QUrl &url); void pkeyAuthResult(KJob *job); QWebEngineView mWebView; QWebEngineProfile mWebProfile; QWebEnginePage mWebPage; QOAuth2AuthorizationCodeFlow mOAuth2; EwsOAuthReplyHandler mReplyHandler; EwsOAuthRequestInterceptor mRequestInterceptor; EwsOAuthUrlSchemeHandler mSchemeHandler; QString mToken; const QString mEmail; const QString mRedirectUri; bool mAuthenticated; QPointer mWebDialog; QString mPKeyPassword; EwsOAuth *q_ptr = nullptr; Q_DECLARE_PUBLIC(EwsOAuth) }; void EwsOAuthUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *request) { Q_EMIT returnUriReceived(request->requestUrl()); } void EwsOAuthReplyHandler::networkReplyFinished(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { Q_EMIT replyError(reply->errorString()); return; } else if (reply->header(QNetworkRequest::ContentTypeHeader).isNull()) { Q_EMIT replyError(QStringLiteral("Empty or no Content-type header")); return; } const auto cth = reply->header(QNetworkRequest::ContentTypeHeader); const auto ct = cth.isNull() ? QStringLiteral("text/html") : cth.toString(); const auto data = reply->readAll(); if (data.isEmpty()) { Q_EMIT replyError(QStringLiteral("No data received")); return; } Q_EMIT replyDataReceived(data); QVariantMap tokens; if (ct.startsWith(QLatin1String("text/html")) || ct.startsWith(QLatin1String("application/x-www-form-urlencoded"))) { QUrlQuery q(QString::fromUtf8(data)); const auto items = q.queryItems(QUrl::FullyDecoded); for (const auto &it : items) { tokens.insert(it.first, it.second); } } else if (ct.startsWith(QLatin1String("application/json")) || ct.startsWith(QLatin1String("text/javascript"))) { const auto document = QJsonDocument::fromJson(data); if (!document.isObject()) { Q_EMIT replyError(QStringLiteral("Invalid JSON data received")); return; } const auto object = document.object(); if (object.isEmpty()) { Q_EMIT replyError(QStringLiteral("Empty JSON data received")); return; } tokens = object.toVariantMap(); } else { Q_EMIT replyError(QStringLiteral("Unknown content type")); return; } const auto error = tokens.value(QStringLiteral("error")); if (error.isValid()) { Q_EMIT replyError(QStringLiteral("Received error response: ") + error.toString()); return; } const auto accessToken = tokens.value(QStringLiteral("access_token")); if (!accessToken.isValid() || accessToken.toString().isEmpty()) { Q_EMIT replyError(QStringLiteral("Received empty or no access token")); return; } Q_EMIT tokensReceived(tokens); } void EwsOAuthRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) { const auto url = info.requestUrl(); qCDebugNC(EWSCLI_LOG) << QStringLiteral("Intercepted browser navigation to ") << url; if ((url.toString(QUrl::RemoveQuery) == mRedirectUri) || (url.toString(QUrl::RemoveQuery) == pkeyRedirectUri) ) { qCDebug(EWSCLI_LOG) << QStringLiteral("Found redirect URI - blocking request"); Q_EMIT redirectUriIntercepted(url); info.block(true); } } EwsOAuthPrivate::EwsOAuthPrivate(EwsOAuth *parent, const QString &email, const QString &appId, const QString &redirectUri) : QObject(nullptr) , mWebView(nullptr) , mWebProfile() , mWebPage(&mWebProfile) , mReplyHandler(this, redirectUri) , mRequestInterceptor(this, redirectUri) , mEmail(email) , mRedirectUri(redirectUri) , mAuthenticated(false) , q_ptr(parent) { mOAuth2.setReplyHandler(&mReplyHandler); mOAuth2.setAuthorizationUrl(o365AuthorizationUrl); mOAuth2.setAccessTokenUrl(o365AccessTokenUrl); mOAuth2.setClientIdentifier(appId); -#if QTWEBENGINEWIDGETS_VERSION < QT_VERSION_CHECK(5, 13, 0) - mWebProfile.setRequestInterceptor(&mRequestInterceptor); -#else mWebProfile.setUrlRequestInterceptor(&mRequestInterceptor); -#endif mWebProfile.installUrlSchemeHandler("urn", &mSchemeHandler); mWebView.setPage(&mWebPage); mOAuth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap *parameters) { modifyParametersFunction(stage, parameters); }); connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &EwsOAuthPrivate::authorizeWithBrowser); connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::granted, this, &EwsOAuthPrivate::granted); connect(&mOAuth2, &QOAuth2AuthorizationCodeFlow::error, this, &EwsOAuthPrivate::error); connect(&mRequestInterceptor, &EwsOAuthRequestInterceptor::redirectUriIntercepted, this, &EwsOAuthPrivate::redirectUriIntercepted, Qt::QueuedConnection); connect(&mReplyHandler, &EwsOAuthReplyHandler::replyError, this, [this](const QString &err) { error(QStringLiteral("Network reply error"), err, QUrl()); }); } bool EwsOAuthPrivate::authenticate(bool interactive) { Q_Q(EwsOAuth); qCInfoNC(EWSCLI_LOG) << QStringLiteral("Starting OAuth2 authentication"); if (!mOAuth2.refreshToken().isEmpty()) { mOAuth2.refreshAccessToken(); return true; } else if (interactive) { mOAuth2.grant(); return true; } else { return false; } } void EwsOAuthPrivate::modifyParametersFunction(QAbstractOAuth::Stage stage, QVariantMap *parameters) { switch (stage) { case QAbstractOAuth::Stage::RequestingAccessToken: parameters->insert(QStringLiteral("resource"), o365Resource); break; case QAbstractOAuth::Stage::RequestingAuthorization: parameters->insert(QStringLiteral("prompt"), QStringLiteral("login")); parameters->insert(QStringLiteral("login_hint"), mEmail); parameters->insert(QStringLiteral("resource"), o365Resource); break; default: break; } } void EwsOAuthPrivate::authorizeWithBrowser(const QUrl &url) { Q_Q(EwsOAuth); qCInfoNC(EWSCLI_LOG) << QStringLiteral("Launching browser for authentication"); /* Bad bad Microsoft... * When Conditional Access is enabled on the server the OAuth2 authentication server only supports Windows, * MacOSX, Android and iOS. No option to include Linux. Support (i.e. guarantee that it works) * is one thing, but blocking unsupported browsers completely is just wrong. * Fortunately enough this can be worked around by faking the user agent to something "supported". */ auto userAgent = o365FakeUserAgent; if (!q->mPKeyCertFile.isNull() && !q->mPKeyKeyFile.isNull()) { qCInfoNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth certificates"); userAgent += pkeyAuthSuffix; } else { qCInfoNC(EWSCLI_LOG) << QStringLiteral("PKeyAuth certificates not found"); } mWebProfile.setHttpUserAgent(userAgent); mWebDialog = new QDialog(q->mAuthParentWidget); mWebDialog->setObjectName(QStringLiteral("Akonadi EWS Resource - Authentication")); mWebDialog->setWindowIcon(QIcon(QStringLiteral("akonadi-ews"))); mWebDialog->resize(400, 500); auto layout = new QHBoxLayout(mWebDialog); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(&mWebView); mWebView.show(); connect(mWebDialog.data(), &QDialog::rejected, this, [this]() { error(QStringLiteral("User cancellation"), QStringLiteral("The authentication browser was closed"), QUrl()); }); mWebView.load(url); mWebDialog->show(); } QVariantMap EwsOAuthPrivate::queryToVarmap(const QUrl &url) { QUrlQuery query(url); QVariantMap varmap; const auto items = query.queryItems(); for (const auto &item : items) { varmap[item.first] = item.second; } return varmap; } void EwsOAuthPrivate::redirectUriIntercepted(const QUrl &url) { qCDebugNC(EWSCLI_LOG) << QStringLiteral("Intercepted redirect URI from browser: ") << url; mWebView.stop(); mWebDialog->hide(); Q_Q(EwsOAuth); if (url.toString(QUrl::RemoveQuery) == pkeyRedirectUri) { qCDebugNC(EWSCLI_LOG) << QStringLiteral("Found PKeyAuth URI"); auto pkeyAuthJob = new EwsPKeyAuthJob(url, q->mPKeyCertFile, q->mPKeyKeyFile, mPKeyPassword, this); connect(pkeyAuthJob, &KJob::result, this, &EwsOAuthPrivate::pkeyAuthResult); pkeyAuthJob->start(); return; } Q_EMIT mOAuth2.authorizationCallbackReceived(queryToVarmap(url)); } void EwsOAuthPrivate::pkeyAuthResult(KJob *j) { EwsPKeyAuthJob *job = qobject_cast(j); qCDebugNC(EWSCLI_LOG) << QStringLiteral("PKeyAuth result: %1").arg(job->error()); QVariantMap varmap; if (job->error() == 0) { varmap = queryToVarmap(job->resultUri()); } else { varmap[QStringLiteral("error")] = job->errorString(); } Q_EMIT mOAuth2.authorizationCallbackReceived(varmap); } void EwsOAuthPrivate::granted() { Q_Q(EwsOAuth); qCInfoNC(EWSCLI_LOG) << QStringLiteral("Authentication succeeded"); mAuthenticated = true; QMap map; map[accessTokenMapKey] = mOAuth2.token(); map[refreshTokenMapKey] = mOAuth2.refreshToken(); Q_EMIT q->setWalletMap(map); Q_EMIT q->authSucceeded(); } void EwsOAuthPrivate::error(const QString &error, const QString &errorDescription, const QUrl &uri) { Q_Q(EwsOAuth); Q_UNUSED(uri); mAuthenticated = false; mOAuth2.setRefreshToken(QString()); qCInfoNC(EWSCLI_LOG) << QStringLiteral("Authentication failed: ") << error << errorDescription; Q_EMIT q->authFailed(error); } EwsOAuth::EwsOAuth(QObject *parent, const QString &email, const QString &appId, const QString &redirectUri) : EwsAbstractAuth(parent) , d_ptr(new EwsOAuthPrivate(this, email, appId, redirectUri)) { } EwsOAuth::~EwsOAuth() { } void EwsOAuth::init() { Q_EMIT requestWalletMap(); } bool EwsOAuth::getAuthData(QString &username, QString &password, QStringList &customHeaders) { Q_D(const EwsOAuth); Q_UNUSED(username); Q_UNUSED(password); if (d->mAuthenticated) { customHeaders.append(QStringLiteral("Authorization: Bearer ") + d->mOAuth2.token()); return true; } else { return false; } } void EwsOAuth::notifyRequestAuthFailed() { Q_D(EwsOAuth); d->mOAuth2.setToken(QString()); d->mAuthenticated = false; EwsAbstractAuth::notifyRequestAuthFailed(); } bool EwsOAuth::authenticate(bool interactive) { Q_D(EwsOAuth); return d->authenticate(interactive); } const QString &EwsOAuth::reauthPrompt() const { static const QString prompt = i18nc("@info", "Microsoft Exchange credentials for the account %1 are no longer valid. You need to authenticate in order to continue using it."); return prompt; } const QString &EwsOAuth::authFailedPrompt() const { static const QString prompt = i18nc("@info", "Failed to obtain credentials for Microsoft Exchange account %1. Please update it in the account settings page."); return prompt; } void EwsOAuth::walletPasswordRequestFinished(const QString &password) { Q_UNUSED(password); } void EwsOAuth::walletMapRequestFinished(const QMap &map) { Q_D(EwsOAuth); if (map.contains(pkeyPasswordMapKey)) { d->mPKeyPassword = map[pkeyPasswordMapKey]; } if (map.contains(refreshTokenMapKey)) { d->mOAuth2.setRefreshToken(map[refreshTokenMapKey]); } if (map.contains(accessTokenMapKey)) { d->mOAuth2.setToken(map[accessTokenMapKey]); d->mAuthenticated = true; Q_EMIT authSucceeded(); } else { Q_EMIT authFailed(QStringLiteral("Access token request failed")); } } #include "ewsoauth.moc" diff --git a/resources/ews/test/unittests/ewsoauth_ut_mock.cpp b/resources/ews/test/unittests/ewsoauth_ut_mock.cpp index 4d74fffc3..a76fb0caf 100644 --- a/resources/ews/test/unittests/ewsoauth_ut_mock.cpp +++ b/resources/ews/test/unittests/ewsoauth_ut_mock.cpp @@ -1,488 +1,480 @@ /* Copyright (C) 2018 Krzysztof Nowicki This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ewsoauth_ut_mock.h" #include #include #include Q_LOGGING_CATEGORY(EWSCLI_LOG, "org.kde.pim.ews.client", QtInfoMsg) namespace Mock { QPointer QWebEngineView::instance; QPointer QOAuth2AuthorizationCodeFlow::instance; QUrl QWebEngineUrlRequestJob::requestUrl() const { return mUrl; } QUrl QWebEngineUrlRequestInfo::requestUrl() const { return mUrl; } void QWebEngineUrlRequestInfo::block(bool) { mBlocked = true; } QWebEngineUrlRequestInterceptor::QWebEngineUrlRequestInterceptor(QObject *parent) : QObject(parent) { } QWebEngineUrlRequestInterceptor::~QWebEngineUrlRequestInterceptor() { } QWebEngineUrlSchemeHandler::QWebEngineUrlSchemeHandler(QObject *parent) : QObject(parent) { } QWebEngineUrlSchemeHandler::~QWebEngineUrlSchemeHandler() { } QWebEngineProfile::QWebEngineProfile(QObject *parent) : QObject(parent) , mInterceptor(nullptr) , mHandler(nullptr) { } QWebEngineProfile::~QWebEngineProfile() { } void QWebEngineProfile::setHttpUserAgent(const QString &ua) { mUserAgent = ua; } -#if QTWEBENGINEWIDGETS_VERSION < QT_VERSION_CHECK(5, 13, 0) -void QWebEngineProfile::setRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor) -{ - mInterceptor = interceptor; -} - -#else void QWebEngineProfile::setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor) { mInterceptor = interceptor; } -#endif void QWebEngineProfile::installUrlSchemeHandler(QByteArray const &scheme, QWebEngineUrlSchemeHandler *handler) { mScheme = QString::fromLatin1(scheme); mHandler = handler; } QWebEnginePage::QWebEnginePage(QWebEngineProfile *profile, QObject *parent) : QObject(parent) , mProfile(profile) { connect(profile, &QWebEngineProfile::logEvent, this, &QWebEnginePage::logEvent); } QWebEnginePage::~QWebEnginePage() { } QWebEngineView::QWebEngineView(QWidget *parent) : QWidget(parent) , mPage(nullptr) { if (!instance) { instance = this; } else { qDebug() << "QWebEngineView instance already exists!"; } } QWebEngineView::~QWebEngineView() { } void QWebEngineView::load(const QUrl &url) { Q_EMIT logEvent(QStringLiteral("LoadWebPage:") + url.toString()); simulatePageLoad(url); QVariantMap params; if (mAuthFunction) { mAuthFunction(url, params); simulatePageLoad(QUrl(mRedirectUri + QStringLiteral("?") + QOAuth2AuthorizationCodeFlow::mapToSortedQuery(params).toString())); } else { qWarning() << "No authentication callback defined"; } } void QWebEngineView::simulatePageLoad(const QUrl &url) { if (mPage && mPage->mProfile && mPage->mProfile->mInterceptor) { QWebEngineUrlRequestInfo info(url, this); Q_EMIT logEvent(QStringLiteral("InterceptRequest:") + url.toString()); mPage->mProfile->mInterceptor->interceptRequest(info); Q_EMIT logEvent(QStringLiteral("InterceptRequestBlocked:%1").arg(info.mBlocked)); } else { qWarning() << "Cannot reach to request interceptor"; } } void QWebEngineView::setPage(QWebEnginePage *page) { mPage = page; connect(page, &QWebEnginePage::logEvent, this, &QWebEngineView::logEvent); } void QWebEngineView::stop() { } void QWebEngineView::setAuthFunction(const AuthFunc &func) { mAuthFunction = func; } void QWebEngineView::setRedirectUri(const QString &uri) { mRedirectUri = uri; } QNetworkReply::NetworkError QNetworkReply::error() const { return NoError; } QVariant QNetworkReply::header(QNetworkRequest::KnownHeaders header) const { return mHeaders[header]; } QAbstractOAuthReplyHandler::QAbstractOAuthReplyHandler(QObject *parent) : QObject(parent) { } QAbstractOAuthReplyHandler::~QAbstractOAuthReplyHandler() { } QAbstractOAuth::QAbstractOAuth(QObject *parent) : QObject(parent) , mStatus(Status::NotAuthenticated) { } void QAbstractOAuth::setReplyHandler(QAbstractOAuthReplyHandler *handler) { mReplyHandler = handler; } void QAbstractOAuth::setAuthorizationUrl(const QUrl &url) { mAuthUrl = url; } void QAbstractOAuth::setClientIdentifier(const QString &identifier) { mClientId = identifier; } void QAbstractOAuth::setModifyParametersFunction(const std::function *)> &func) { mModifyParamsFunc = func; } QString QAbstractOAuth::token() const { return mToken; } void QAbstractOAuth::setToken(const QString &token) { mToken = token; } QAbstractOAuth::Status QAbstractOAuth::status() const { return mStatus; } QAbstractOAuth2::QAbstractOAuth2(QObject *parent) : QAbstractOAuth(parent) { } QString QAbstractOAuth2::refreshToken() const { return mRefreshToken; } void QAbstractOAuth2::setRefreshToken(const QString &token) { mRefreshToken = token; } QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QObject *parent) : QAbstractOAuth2(parent) { if (!instance) { instance = this; } else { qDebug() << "QOAuth2AuthorizationCodeFlow instance already exists!"; } } QOAuth2AuthorizationCodeFlow::~QOAuth2AuthorizationCodeFlow() { } void QOAuth2AuthorizationCodeFlow::setAccessTokenUrl(const QUrl &url) { mTokenUrl = url; } void QOAuth2AuthorizationCodeFlow::grant() { QMap map; map[QStringLiteral("response_type")] = QStringLiteral("code"); map[QStringLiteral("client_id")] = QUrl::toPercentEncoding(mClientId); map[QStringLiteral("redirect_uri")] = QUrl::toPercentEncoding(mReplyHandler->callback()); map[QStringLiteral("scope")] = QString(); map[QStringLiteral("state")] = mState; Q_EMIT logEvent(QStringLiteral("ModifyParams:RequestingAuthorization:") + mapToSortedQuery(map).toString()); if (mModifyParamsFunc) { mModifyParamsFunc(Stage::RequestingAuthorization, &map); } mResource = QUrl::fromPercentEncoding(map[QStringLiteral("resource")].toByteArray()); QUrl url(mAuthUrl); url.setQuery(mapToSortedQuery(map)); Q_EMIT logEvent(QStringLiteral("AuthorizeWithBrowser:") + url.toString()); connect(this, &QAbstractOAuth2::authorizationCallbackReceived, this, &QOAuth2AuthorizationCodeFlow::authCallbackReceived, Qt::UniqueConnection); Q_EMIT authorizeWithBrowser(url); } void QOAuth2AuthorizationCodeFlow::refreshAccessToken() { mStatus = Status::RefreshingToken; doRefreshAccessToken(); } void QOAuth2AuthorizationCodeFlow::doRefreshAccessToken() { QMap map; map[QStringLiteral("grant_type")] = QStringLiteral("authorization_code"); map[QStringLiteral("code")] = QUrl::toPercentEncoding(mRefreshToken); map[QStringLiteral("client_id")] = QUrl::toPercentEncoding(mClientId); map[QStringLiteral("redirect_uri")] = QUrl::toPercentEncoding(mReplyHandler->callback()); Q_EMIT logEvent(QStringLiteral("ModifyParams:RequestingAccessToken:") + mapToSortedQuery(map).toString()); if (mModifyParamsFunc) { mModifyParamsFunc(Stage::RequestingAccessToken, &map); } connect(mReplyHandler, &QAbstractOAuthReplyHandler::tokensReceived, this, &QOAuth2AuthorizationCodeFlow::tokenCallbackReceived, Qt::UniqueConnection); connect(mReplyHandler, &QAbstractOAuthReplyHandler::replyDataReceived, this, &QOAuth2AuthorizationCodeFlow::replyDataCallbackReceived, Qt::UniqueConnection); if (mTokenFunc) { QNetworkReply reply(this); QString data; reply.mError = mTokenFunc(data, reply.mHeaders); reply.setData(data.toUtf8()); reply.open(QIODevice::ReadOnly); Q_EMIT logEvent(QStringLiteral("NetworkReplyFinished:") + data); mReplyHandler->networkReplyFinished(&reply); } else { qWarning() << "No token function defined"; } } QUrlQuery QOAuth2AuthorizationCodeFlow::mapToSortedQuery(QMap const &map) { QUrlQuery query; QStringList keys = map.keys(); keys.sort(); for (const auto &key : qAsConst(keys)) { query.addQueryItem(key, map[key].toString()); } return query; } void QOAuth2AuthorizationCodeFlow::authCallbackReceived(QMap const ¶ms) { Q_EMIT logEvent(QStringLiteral("AuthorizatioCallbackReceived:") + mapToSortedQuery(params).toString()); mRefreshToken = params[QStringLiteral("code")].toString(); if (!mRefreshToken.isEmpty()) { mStatus = Status::TemporaryCredentialsReceived; doRefreshAccessToken(); } else { Q_EMIT error(QString(), QString(), QUrl()); } } void QOAuth2AuthorizationCodeFlow::tokenCallbackReceived(const QVariantMap &tokens) { Q_EMIT logEvent(QStringLiteral("TokenCallback:") + mapToSortedQuery(tokens).toString()); mToken = tokens[QStringLiteral("access_token")].toString(); mRefreshToken = tokens[QStringLiteral("refresh_token")].toString(); mStatus = Status::Granted; Q_EMIT granted(); } void QOAuth2AuthorizationCodeFlow::replyDataCallbackReceived(const QByteArray &data) { Q_EMIT logEvent(QStringLiteral("ReplyDataCallback:") + QString::fromLatin1(data)); } QString QOAuth2AuthorizationCodeFlow::redirectUri() const { return mReplyHandler->callback(); } void QOAuth2AuthorizationCodeFlow::setTokenFunction(const TokenFunc &func) { mTokenFunc = func; } void QOAuth2AuthorizationCodeFlow::setState(const QString &state) { mState = state; } QString browserDisplayRequestString() { return QStringLiteral("BrowserDisplayRequest"); } QString modifyParamsAuthString(const QString &clientId, const QString &returnUri, const QString &state) { return QStringLiteral("ModifyParams:RequestingAuthorization:client_id=%1&redirect_uri=%2&response_type=code&scope&state=%3") .arg(QString::fromUtf8(QUrl::toPercentEncoding(clientId)), QString::fromLatin1(QUrl::toPercentEncoding(returnUri)), QString::fromLatin1(QUrl::toPercentEncoding(state))); } QString authUrlString(const QString &authUrl, const QString &clientId, const QString &returnUri, const QString &email, const QString &resource, const QString &state) { return QStringLiteral("%1?client_id=%2&login_hint=%3&prompt=login&redirect_uri=%4&resource=%5&response_type=code&scope&state=%6") .arg(authUrl, QString::fromLatin1(QUrl::toPercentEncoding(clientId)), email, QString::fromLatin1(QUrl::toPercentEncoding(returnUri)), QString::fromLatin1(QUrl::toPercentEncoding(resource)), QString::fromLatin1(QUrl::toPercentEncoding(state))); } QString authorizeWithBrowserString(const QString &url) { return QStringLiteral("AuthorizeWithBrowser:") + url; } QString loadWebPageString(const QString &url) { return QStringLiteral("LoadWebPage:") + url; } QString interceptRequestString(const QString &url) { return QStringLiteral("InterceptRequest:") + url; } QString interceptRequestBlockedString(bool blocked) { return QStringLiteral("InterceptRequestBlocked:%1").arg(blocked); } QString authorizationCallbackReceivedString(const QString &code) { return QStringLiteral("AuthorizatioCallbackReceived:code=%1").arg(code); } QString modifyParamsTokenString(const QString &clientId, const QString &returnUri, const QString &code) { return QStringLiteral("ModifyParams:RequestingAccessToken:client_id=%1&code=%2&grant_type=authorization_code&redirect_uri=%3") .arg(QString::fromUtf8(QUrl::toPercentEncoding(clientId)), QString::fromLatin1(QUrl::toPercentEncoding(code)), QString::fromLatin1(QUrl::toPercentEncoding(returnUri))); } QString networkReplyFinishedString(const QString &data) { return QStringLiteral("NetworkReplyFinished:") + data; } QString replyDataCallbackString(const QString &data) { return QStringLiteral("ReplyDataCallback:") + data; } QString tokenCallbackString(const QString &accessToken, const QString &refreshToken, const QString &idToken, quint64 time, unsigned int tokenLifetime, unsigned int extTokenLifetime, const QString &resource) { return QStringLiteral( "TokenCallback:access_token=%1&expires_in=%2&expires_on=%3&ext_expires_in=%4&foci=1&id_token=%5¬_before=%6&refresh_token=%7&resource=%8&scope=ReadWrite.All&token_type=Bearer") .arg(accessToken).arg(tokenLifetime).arg(time + tokenLifetime).arg(extTokenLifetime).arg(idToken).arg(time) .arg(refreshToken).arg(resource); } QString requestWalletMapString() { return QStringLiteral("RequestWalletMap"); } const QString &KJob::errorString() const { static const QString empty; return empty; } EwsPKeyAuthJob::EwsPKeyAuthJob(const QUrl &pkeyUri, const QString &certFile, const QString &keyFile, const QString &keyPassword, QObject *parent) : KJob(parent) { Q_UNUSED(pkeyUri); Q_UNUSED(certFile); Q_UNUSED(keyFile); Q_UNUSED(keyPassword); } const QUrl &EwsPKeyAuthJob::resultUri() const { static const QUrl empty; return empty; } } diff --git a/resources/ews/test/unittests/ewsoauth_ut_mock.h b/resources/ews/test/unittests/ewsoauth_ut_mock.h index 4a8b0b479..8103c2175 100644 --- a/resources/ews/test/unittests/ewsoauth_ut_mock.h +++ b/resources/ews/test/unittests/ewsoauth_ut_mock.h @@ -1,341 +1,337 @@ /* Copyright (C) 2018 Krzysztof Nowicki This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EWSOAUTH_UT_MOCK_H #define EWSOAUTH_UT_MOCK_H #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(EWSCLI_LOG) namespace Mock { class QWebEngineUrlRequestJob : public QObject { Q_OBJECT public: explicit QWebEngineUrlRequestJob(const QUrl &url, QObject *parent) : QObject(parent) , mUrl(url) { } ~QWebEngineUrlRequestJob() override = default; QUrl requestUrl() const; QUrl mUrl; }; class QWebEngineUrlRequestInfo : public QObject { Q_OBJECT public: explicit QWebEngineUrlRequestInfo(const QUrl &url, QObject *parent) : QObject(parent) , mBlocked(false) , mUrl(url) { } ~QWebEngineUrlRequestInfo() override = default; QUrl requestUrl() const; void block(bool shouldBlock); bool mBlocked; QUrl mUrl; }; class QWebEngineUrlRequestInterceptor : public QObject { Q_OBJECT public: explicit QWebEngineUrlRequestInterceptor(QObject *parent); ~QWebEngineUrlRequestInterceptor() override; virtual void interceptRequest(QWebEngineUrlRequestInfo &info) = 0; }; class QWebEngineUrlSchemeHandler : public QObject { Q_OBJECT public: QWebEngineUrlSchemeHandler(QObject *parent); ~QWebEngineUrlSchemeHandler() override; virtual void requestStarted(QWebEngineUrlRequestJob *request) = 0; }; class QWebEngineProfile : public QObject { Q_OBJECT public: explicit QWebEngineProfile(QObject *parent = nullptr); ~QWebEngineProfile() override; void setHttpUserAgent(const QString &ua); -#if QTWEBENGINEWIDGETS_VERSION < QT_VERSION_CHECK(5, 13, 0) - void setRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); -#else void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); -#endif void installUrlSchemeHandler(QByteArray const &scheme, QWebEngineUrlSchemeHandler *handler); Q_SIGNALS: void logEvent(const QString &event); public: QString mUserAgent; QWebEngineUrlRequestInterceptor *mInterceptor; QString mScheme; QWebEngineUrlSchemeHandler *mHandler; }; class QWebEnginePage : public QObject { Q_OBJECT public: explicit QWebEnginePage(QWebEngineProfile *profile, QObject *parent = nullptr); ~QWebEnginePage() override; Q_SIGNALS: void logEvent(const QString &event); public: QWebEngineProfile *mProfile; }; class QWebEngineView : public QWidget { Q_OBJECT public: typedef std::function AuthFunc; explicit QWebEngineView(QWidget *parent); ~QWebEngineView() override; void load(const QUrl &url); void setPage(QWebEnginePage *page); void stop(); void setAuthFunction(const AuthFunc &func); void setRedirectUri(const QString &uri); static QPointer instance; Q_SIGNALS: void logEvent(const QString &event); protected: void simulatePageLoad(const QUrl &url); QWebEnginePage *mPage; QString mRedirectUri; AuthFunc mAuthFunction; }; class QNetworkRequest { public: enum KnownHeaders { ContentTypeHeader }; }; class QNetworkReply : public QBuffer { Q_OBJECT public: enum NetworkError { NoError = 0, }; Q_ENUM(NetworkError) explicit QNetworkReply(QObject *parent) : QBuffer(parent) { } ~QNetworkReply() override = default; NetworkError error() const; QVariant header(QNetworkRequest::KnownHeaders header) const; QMap mHeaders; NetworkError mError; }; class QAbstractOAuthReplyHandler : public QObject { Q_OBJECT public: QAbstractOAuthReplyHandler(QObject *parent); ~QAbstractOAuthReplyHandler() override; virtual QString callback() const = 0; virtual void networkReplyFinished(QNetworkReply *reply) = 0; Q_SIGNALS: void replyDataReceived(const QByteArray &data); void tokensReceived(const QVariantMap &tokens); }; class QAbstractOAuth : public QObject { Q_OBJECT public: Q_ENUMS(Stage) enum class Stage { RequestingTemporaryCredentials, RequestingAuthorization, RequestingAccessToken, RefreshingAccessToken }; Q_ENUMS(Status) enum class Status { NotAuthenticated, TemporaryCredentialsReceived, Granted, RefreshingToken }; explicit QAbstractOAuth(QObject *parent); ~QAbstractOAuth() override = default; void setReplyHandler(QAbstractOAuthReplyHandler *handler); void setAuthorizationUrl(const QUrl &url); void setClientIdentifier(const QString &identifier); void setModifyParametersFunction(const std::function *)> &func); QString token() const; void setToken(const QString &token); Status status() const; Q_SIGNALS: void authorizeWithBrowser(const QUrl &url); void granted(); void logEvent(const QString &event); protected: QAbstractOAuthReplyHandler *mReplyHandler; QUrl mAuthUrl; QString mClientId; std::function *)> mModifyParamsFunc; QString mToken; QString mRefreshToken; QUrl mTokenUrl; QString mResource; Status mStatus; }; class QAbstractOAuth2 : public QAbstractOAuth { Q_OBJECT public: explicit QAbstractOAuth2(QObject *parent); ~QAbstractOAuth2() override = default; QString refreshToken() const; void setRefreshToken(const QString &token); Q_SIGNALS: void authorizationCallbackReceived(QMap const ¶ms); void error(const QString &error, const QString &errorDescription, const QUrl &uri); }; class QOAuth2AuthorizationCodeFlow : public QAbstractOAuth2 { Q_OBJECT public: typedef std::function &)> TokenFunc; explicit QOAuth2AuthorizationCodeFlow(QObject *parent = nullptr); ~QOAuth2AuthorizationCodeFlow() override; void setAccessTokenUrl(const QUrl &url); void grant(); void refreshAccessToken(); QString redirectUri() const; void setTokenFunction(const TokenFunc &func); void setState(const QString &state); static QUrlQuery mapToSortedQuery(QMap const &map); static QPointer instance; protected: void authCallbackReceived(QMap const ¶ms); void replyDataCallbackReceived(const QByteArray &data); void tokenCallbackReceived(const QVariantMap &tokens); void doRefreshAccessToken(); TokenFunc mTokenFunc; QString mState; }; class KJob : public QObject { Q_OBJECT public: explicit KJob(QObject *) { } ~KJob() override = default; int error() const { return 0; } const QString &errorString() const; Q_SIGNALS: void result(KJob *job); }; class EwsPKeyAuthJob : public KJob { Q_OBJECT public: explicit EwsPKeyAuthJob(const QUrl &pkeyUri, const QString &certFile, const QString &keyFile, const QString &keyPassword, QObject *parent); ~EwsPKeyAuthJob() override = default; void start() { } const QUrl &resultUri() const; }; QString browserDisplayRequestString(); QString modifyParamsAuthString(const QString &clientId, const QString &returnUri, const QString &state); QString authUrlString(const QString &authUrl, const QString &clientId, const QString &returnUri, const QString &email, const QString &resource, const QString &state); QString authorizeWithBrowserString(const QString &url); QString loadWebPageString(const QString &url); QString interceptRequestString(const QString &url); QString interceptRequestBlockedString(bool blocked); QString authorizationCallbackReceivedString(const QString &code); QString modifyParamsTokenString(const QString &clientId, const QString &returnUri, const QString &code); QString networkReplyFinishedString(const QString &data); QString replyDataCallbackString(const QString &data); QString tokenCallbackString(const QString &accessToken, const QString &refreshToken, const QString &idToken, quint64 time, unsigned int tokenLifetime, unsigned int extTokenLifetime, const QString &resource); QString requestWalletMapString(); } #endif /* EWSOAUTH_UT_MOCK_H */ diff --git a/resources/kolab/pimkolab/conversion/kcalconversion.cpp b/resources/kolab/pimkolab/conversion/kcalconversion.cpp index 44ed3aee1..05bf4f3f5 100644 --- a/resources/kolab/pimkolab/conversion/kcalconversion.cpp +++ b/resources/kolab/pimkolab/conversion/kcalconversion.cpp @@ -1,947 +1,919 @@ /* * Copyright (C) 2011 Christian Mollekopf * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "kcalconversion.h" #include #include #include #include #include #include "commonconversion.h" #include "pimkolab_debug.h" namespace Kolab { namespace Conversion { //The uid of a contact which refers to the uuid of a contact in the addressbook #define CUSTOM_KOLAB_CONTACT_UUID "X_KOLAB_CONTACT_UUID" #define CUSTOM_KOLAB_CONTACT_CUTYPE "X_KOLAB_CONTACT_CUTYPE" #define CUSTOM_KOLAB_URL "X-KOLAB-URL" KCalendarCore::Duration toDuration(const Kolab::Duration &d) { int value = 0; if (d.hours() || d.minutes() || d.seconds()) { value = ((((d.weeks() * 7 + d.days()) * 24 + d.hours()) * 60 + d.minutes()) * 60 + d.seconds()); if (d.isNegative()) { value = -value; } return KCalendarCore::Duration(value); } value = d.weeks() * 7 + d.days(); if (d.isNegative()) { value = -value; } return KCalendarCore::Duration(value, KCalendarCore::Duration::Days); } Kolab::Duration fromDuration(const KCalendarCore::Duration &d) { int value = d.value(); bool isNegative = false; if (value < 0) { isNegative = true; value = -value; } //We don't know how the seconds/days were distributed before, so no point in distributing them (probably) if (d.isDaily()) { int days = value; return Kolab::Duration(days, 0, 0, 0, isNegative); } int seconds = value; // int minutes = seconds / 60; // seconds = seconds % 60; // int hours = minutes / 60; // minutes = minutes % 60; return Kolab::Duration(0, 0, 0, seconds, isNegative); } KCalendarCore::Incidence::Secrecy toSecrecy(Kolab::Classification c) { switch (c) { case Kolab::ClassPublic: return KCalendarCore::Incidence::SecrecyPublic; case Kolab::ClassPrivate: return KCalendarCore::Incidence::SecrecyPrivate; case Kolab::ClassConfidential: return KCalendarCore::Incidence::SecrecyConfidential; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return KCalendarCore::Incidence::SecrecyPublic; } Kolab::Classification fromSecrecy(KCalendarCore::Incidence::Secrecy c) { switch (c) { case KCalendarCore::Incidence::SecrecyPublic: return Kolab::ClassPublic; case KCalendarCore::Incidence::SecrecyPrivate: return Kolab::ClassPrivate; case KCalendarCore::Incidence::SecrecyConfidential: return Kolab::ClassConfidential; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return Kolab::ClassPublic; } int toPriority(int priority) { //Same mapping return priority; } int fromPriority(int priority) { //Same mapping return priority; } KCalendarCore::Incidence::Status toStatus(Kolab::Status s) { switch (s) { case Kolab::StatusUndefined: return KCalendarCore::Incidence::StatusNone; case Kolab::StatusNeedsAction: return KCalendarCore::Incidence::StatusNeedsAction; case Kolab::StatusCompleted: return KCalendarCore::Incidence::StatusCompleted; case Kolab::StatusInProcess: return KCalendarCore::Incidence::StatusInProcess; case Kolab::StatusCancelled: return KCalendarCore::Incidence::StatusCanceled; case Kolab::StatusTentative: return KCalendarCore::Incidence::StatusTentative; case Kolab::StatusConfirmed: return KCalendarCore::Incidence::StatusConfirmed; case Kolab::StatusDraft: return KCalendarCore::Incidence::StatusDraft; case Kolab::StatusFinal: return KCalendarCore::Incidence::StatusFinal; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return KCalendarCore::Incidence::StatusNone; } Kolab::Status fromStatus(KCalendarCore::Incidence::Status s) { switch (s) { case KCalendarCore::Incidence::StatusNone: return Kolab::StatusUndefined; case KCalendarCore::Incidence::StatusNeedsAction: return Kolab::StatusNeedsAction; case KCalendarCore::Incidence::StatusCompleted: return Kolab::StatusCompleted; case KCalendarCore::Incidence::StatusInProcess: return Kolab::StatusInProcess; case KCalendarCore::Incidence::StatusCanceled: return Kolab::StatusCancelled; case KCalendarCore::Incidence::StatusTentative: return Kolab::StatusTentative; case KCalendarCore::Incidence::StatusConfirmed: return Kolab::StatusConfirmed; case KCalendarCore::Incidence::StatusDraft: return Kolab::StatusDraft; case KCalendarCore::Incidence::StatusFinal: return Kolab::StatusFinal; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return Kolab::StatusUndefined; } KCalendarCore::Attendee::PartStat toPartStat(Kolab::PartStatus p) { switch (p) { case Kolab::PartNeedsAction: return KCalendarCore::Attendee::NeedsAction; case Kolab::PartAccepted: return KCalendarCore::Attendee::Accepted; case Kolab::PartDeclined: return KCalendarCore::Attendee::Declined; case Kolab::PartTentative: return KCalendarCore::Attendee::Tentative; case Kolab::PartDelegated: return KCalendarCore::Attendee::Delegated; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return KCalendarCore::Attendee::NeedsAction; } Kolab::PartStatus fromPartStat(KCalendarCore::Attendee::PartStat p) { switch (p) { case KCalendarCore::Attendee::NeedsAction: return Kolab::PartNeedsAction; case KCalendarCore::Attendee::Accepted: return Kolab::PartAccepted; case KCalendarCore::Attendee::Declined: return Kolab::PartDeclined; case KCalendarCore::Attendee::Tentative: return Kolab::PartTentative; case KCalendarCore::Attendee::Delegated: return Kolab::PartDelegated; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return Kolab::PartNeedsAction; } KCalendarCore::Attendee::Role toRole(Kolab::Role r) { switch (r) { case Kolab::Required: return KCalendarCore::Attendee::ReqParticipant; case Kolab::Chair: return KCalendarCore::Attendee::Chair; case Kolab::Optional: return KCalendarCore::Attendee::OptParticipant; case Kolab::NonParticipant: return KCalendarCore::Attendee::NonParticipant; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return KCalendarCore::Attendee::ReqParticipant; } Kolab::Role fromRole(KCalendarCore::Attendee::Role r) { switch (r) { case KCalendarCore::Attendee::ReqParticipant: return Kolab::Required; case KCalendarCore::Attendee::Chair: return Kolab::Chair; case KCalendarCore::Attendee::OptParticipant: return Kolab::Optional; case KCalendarCore::Attendee::NonParticipant: return Kolab::NonParticipant; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return Kolab::Required; } template QString getCustomProperty(const QString &id, const T &e) { std::vector &props = e.customProperties(); foreach (const Kolab::CustomProperty &p, props) { if (fromStdString(p.identifier) == id) { return fromStdString(p.value); } } } template void setIncidence(KCalendarCore::Incidence &i, const T &e) { if (!e.uid().empty()) { i.setUid(fromStdString(e.uid())); } i.setCreated(toDate(e.created())); i.setLastModified(toDate(e.lastModified())); i.setRevision(e.sequence()); i.setSecrecy(toSecrecy(e.classification())); i.setCategories(toStringList(e.categories())); if (e.start().isValid()) { i.setDtStart(toDate(e.start())); i.setAllDay(e.start().isDateOnly()); } i.setSummary(fromStdString(e.summary())); //TODO detect richtext i.setDescription(fromStdString(e.description())); //TODO detect richtext i.setStatus(toStatus(e.status())); foreach (const Kolab::Attendee &a, e.attendees()) { /* * KCalendarCore always sets a UID if empty, but that's just a pointer, and not the uid of a real contact. * Since that means the semantics of the two are different, we have to store the kolab uid as a custom property. */ KCalendarCore::Attendee attendee(fromStdString(a.contact().name()), fromStdString(a.contact().email()), a.rsvp(), toPartStat(a.partStat()), toRole(a.role())); if (!a.contact().uid().empty()) { //TODO Identify contact from addressbook based on uid attendee.customProperties().setNonKDECustomProperty(CUSTOM_KOLAB_CONTACT_UUID, fromStdString(a.contact().uid())); } if (!a.delegatedTo().empty()) { if (a.delegatedTo().size() > 1) { qCWarning(PIMKOLAB_LOG) << "multiple delegatees are not supported"; } attendee.setDelegate(toMailto(a.delegatedTo().front().email(), a.delegatedTo().front().name()).toString()); } if (!a.delegatedFrom().empty()) { if (a.delegatedFrom().size() > 1) { qCWarning(PIMKOLAB_LOG) << "multiple delegators are not supported"; } attendee.setDelegator(toMailto(a.delegatedFrom().front().email(), a.delegatedFrom().front().name()).toString()); } if (a.cutype() != Kolab::CutypeIndividual) { attendee.customProperties().setNonKDECustomProperty(CUSTOM_KOLAB_CONTACT_CUTYPE, QString::number(a.cutype())); } i.addAttendee(attendee); } foreach (const Kolab::Attachment &a, e.attachments()) { KCalendarCore::Attachment att; if (!a.uri().empty()) { att = KCalendarCore::Attachment(fromStdString(a.uri()), fromStdString(a.mimetype())); } else { att = KCalendarCore::Attachment(QByteArray::fromRawData(a.data().c_str(), a.data().size()).toBase64(), fromStdString(a.mimetype())); } if (!a.label().empty()) { att.setLabel(fromStdString(a.label())); } i.addAttachment(att); } QMap props; foreach (const Kolab::CustomProperty &prop, e.customProperties()) { QString key; if (prop.identifier.compare(0, 5, "X-KDE")) { key.append(QLatin1String("X-KOLAB-")); } key.append(fromStdString(prop.identifier)); props.insert(key.toLatin1(), fromStdString(prop.value)); // i.setCustomProperty("KOLAB", fromStdString(prop.identifier).toLatin1(), fromStdString(prop.value)); } i.setCustomProperties(props); } template void getIncidence(T &i, const I &e) { i.setUid(toStdString(e.uid())); i.setCreated(fromDate(e.created(), false)); i.setLastModified(fromDate(e.lastModified(), false)); i.setSequence(e.revision()); i.setClassification(fromSecrecy(e.secrecy())); i.setCategories(fromStringList(e.categories())); i.setStart(fromDate(e.dtStart(), e.allDay())); i.setSummary(toStdString(e.summary())); i.setDescription(toStdString(e.description())); i.setStatus(fromStatus(e.status())); std::vector attendees; foreach (const KCalendarCore::Attendee &ptr, e.attendees()) { const QString &uid = ptr.customProperties().nonKDECustomProperty(CUSTOM_KOLAB_CONTACT_UUID); Kolab::Attendee a(Kolab::ContactReference(toStdString(ptr.email()), toStdString(ptr.name()), toStdString(uid))); a.setRSVP(ptr.RSVP()); a.setPartStat(fromPartStat(ptr.status())); a.setRole(fromRole(ptr.role())); if (!ptr.delegate().isEmpty()) { std::string name; const std::string &email = fromMailto(QUrl(ptr.delegate()), name); a.setDelegatedTo(std::vector() << Kolab::ContactReference(email, name)); } if (!ptr.delegator().isEmpty()) { std::string name; const std::string &email = fromMailto(QUrl(ptr.delegator()), name); a.setDelegatedFrom(std::vector() << Kolab::ContactReference(email, name)); } const QString &cutype = ptr.customProperties().nonKDECustomProperty(CUSTOM_KOLAB_CONTACT_CUTYPE); if (!cutype.isEmpty()) { a.setCutype(static_cast(cutype.toInt())); } attendees.push_back(a); } i.setAttendees(attendees); std::vector attachments; foreach (const KCalendarCore::Attachment &att, e.attachments()) { Kolab::Attachment a; if (att.isUri()) { a.setUri(toStdString(att.uri()), toStdString(att.mimeType())); } else { a.setData(std::string(att.decodedData().data(), att.decodedData().size()), toStdString(att.mimeType())); } a.setLabel(toStdString(att.label())); attachments.push_back(a); } i.setAttachments(attachments); std::vector customProperties; const QMap &props = e.customProperties(); for (QMap::const_iterator it = props.begin(), end(props.end()); it != end; ++it) { QString key(QString::fromUtf8(it.key())); if (key == QLatin1String(CUSTOM_KOLAB_URL)) { continue; } customProperties.push_back(Kolab::CustomProperty(toStdString(key.remove(QStringLiteral("X-KOLAB-"))), toStdString(it.value()))); } i.setCustomProperties(customProperties); } int toWeekDay(Kolab::Weekday wday) { switch (wday) { case Kolab::Monday: return 1; case Kolab::Tuesday: return 2; case Kolab::Wednesday: return 3; case Kolab::Thursday: return 4; case Kolab::Friday: return 5; case Kolab::Saturday: return 6; case Kolab::Sunday: return 7; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return 1; } Kolab::Weekday fromWeekDay(int wday) { switch (wday) { case 1: return Kolab::Monday; case 2: return Kolab::Tuesday; case 3: return Kolab::Wednesday; case 4: return Kolab::Thursday; case 5: return Kolab::Friday; case 6: return Kolab::Saturday; case 7: return Kolab::Sunday; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return Kolab::Monday; } KCalendarCore::RecurrenceRule::PeriodType toRecurrenceType(Kolab::RecurrenceRule::Frequency freq) { switch (freq) { case Kolab::RecurrenceRule::FreqNone: qCWarning(PIMKOLAB_LOG) <<"no recurrence?"; break; case Kolab::RecurrenceRule::Yearly: return KCalendarCore::RecurrenceRule::rYearly; case Kolab::RecurrenceRule::Monthly: return KCalendarCore::RecurrenceRule::rMonthly; case Kolab::RecurrenceRule::Weekly: return KCalendarCore::RecurrenceRule::rWeekly; case Kolab::RecurrenceRule::Daily: return KCalendarCore::RecurrenceRule::rDaily; case Kolab::RecurrenceRule::Hourly: return KCalendarCore::RecurrenceRule::rHourly; case Kolab::RecurrenceRule::Minutely: return KCalendarCore::RecurrenceRule::rMinutely; case Kolab::RecurrenceRule::Secondly: return KCalendarCore::RecurrenceRule::rSecondly; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return KCalendarCore::RecurrenceRule::rNone; } Kolab::RecurrenceRule::Frequency fromRecurrenceType(KCalendarCore::RecurrenceRule::PeriodType freq) { switch (freq) { case KCalendarCore::RecurrenceRule::rNone: qCWarning(PIMKOLAB_LOG) <<"no recurrence?"; break; case KCalendarCore::RecurrenceRule::rYearly: return Kolab::RecurrenceRule::Yearly; case KCalendarCore::RecurrenceRule::rMonthly: return Kolab::RecurrenceRule::Monthly; case KCalendarCore::RecurrenceRule::rWeekly: return Kolab::RecurrenceRule::Weekly; case KCalendarCore::RecurrenceRule::rDaily: return Kolab::RecurrenceRule::Daily; case KCalendarCore::RecurrenceRule::rHourly: return Kolab::RecurrenceRule::Hourly; case KCalendarCore::RecurrenceRule::rMinutely: return Kolab::RecurrenceRule::Minutely; case KCalendarCore::RecurrenceRule::rSecondly: return Kolab::RecurrenceRule::Secondly; default: qCCritical(PIMKOLAB_LOG) << "unhandled"; Q_ASSERT(0); } return Kolab::RecurrenceRule::FreqNone; } KCalendarCore::RecurrenceRule::WDayPos toWeekDayPos(const Kolab::DayPos &dp) { return KCalendarCore::RecurrenceRule::WDayPos(dp.occurence(), toWeekDay(dp.weekday())); } Kolab::DayPos fromWeekDayPos(const KCalendarCore::RecurrenceRule::WDayPos &dp) { return Kolab::DayPos(dp.pos(), fromWeekDay(dp.day())); } template void setRecurrence(KCalendarCore::Incidence &e, const T &event) { const Kolab::RecurrenceRule &rrule = event.recurrenceRule(); if (rrule.isValid()) { KCalendarCore::Recurrence *rec = e.recurrence(); KCalendarCore::RecurrenceRule *defaultRR = rec->defaultRRule(true); Q_ASSERT(defaultRR); defaultRR->setWeekStart(toWeekDay(rrule.weekStart())); defaultRR->setRecurrenceType(toRecurrenceType(rrule.frequency())); defaultRR->setFrequency(rrule.interval()); if (rrule.end().isValid()) { rec->setEndDateTime(toDate(rrule.end())); //TODO date/datetime setEndDate(). With date-only the start date has to be taken into account. } else { rec->setDuration(rrule.count()); } if (!rrule.bysecond().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setBySeconds(QVector::fromStdVector(rrule.bysecond()).toList()); #else const std::vector bySecond = rrule.bysecond(); const QVector stdVector = QVector(bySecond.begin(), bySecond.end()); defaultRR->setBySeconds(stdVector.toList()); #endif } if (!rrule.byminute().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setByMinutes(QVector::fromStdVector(rrule.byminute()).toList()); #else const std::vector byMinutes = rrule.byminute(); const QVector stdVector = QVector(byMinutes.begin(), byMinutes.end()); defaultRR->setByMinutes(stdVector.toList()); #endif } if (!rrule.byhour().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setByHours(QVector::fromStdVector(rrule.byhour()).toList()); #else const std::vector byHours = rrule.byhour(); const QVector stdVector = QVector(byHours.begin(), byHours.end()); defaultRR->setByHours(stdVector.toList()); #endif } if (!rrule.byday().empty()) { QList daypos; foreach (const Kolab::DayPos &dp, rrule.byday()) { daypos.append(toWeekDayPos(dp)); } defaultRR->setByDays(daypos); } if (!rrule.bymonthday().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setByMonthDays(QVector::fromStdVector(rrule.bymonthday()).toList()); #else const std::vector byMonthDays = rrule.bymonthday(); const QVector stdVector = QVector(byMonthDays.begin(), byMonthDays.end()); defaultRR->setByMonthDays(stdVector.toList()); #endif } if (!rrule.byyearday().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setByYearDays(QVector::fromStdVector(rrule.byyearday()).toList()); #else const std::vector byYearDays = rrule.byyearday(); const QVector stdVector = QVector(byYearDays.begin(), byYearDays.end()); defaultRR->setByYearDays(stdVector.toList()); #endif } if (!rrule.byweekno().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setByWeekNumbers(QVector::fromStdVector(rrule.byweekno()).toList()); #else const std::vector byWeekNumbers = rrule.byweekno(); const QVector stdVector = QVector(byWeekNumbers.begin(), byWeekNumbers.end()); defaultRR->setByWeekNumbers(stdVector.toList()); #endif } if (!rrule.bymonth().empty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) defaultRR->setByMonths(QVector::fromStdVector(rrule.bymonth()).toList()); #else const std::vector byMonths = rrule.bymonth(); const QVector stdVector = QVector(byMonths.begin(), byMonths.end()); defaultRR->setByMonths(stdVector.toList()); #endif } } foreach (const Kolab::cDateTime &dt, event.recurrenceDates()) { const QDateTime &date = toDate(dt); if (dt.isDateOnly()) { e.recurrence()->addRDate(date.date()); } else { e.recurrence()->addRDateTime(date); } } foreach (const Kolab::cDateTime &dt, event.exceptionDates()) { const QDateTime &date = toDate(dt); if (dt.isDateOnly()) { e.recurrence()->addExDate(date.date()); } else { e.recurrence()->addExDateTime(date); } } } template void getRecurrence(T &i, const I &e) { if (!e.recurs()) { return; } KCalendarCore::Recurrence *rec = e.recurrence(); KCalendarCore::RecurrenceRule *defaultRR = rec->defaultRRule(false); if (!defaultRR) { qCWarning(PIMKOLAB_LOG) <<"no recurrence"; return; } Q_ASSERT(defaultRR); Kolab::RecurrenceRule rrule; rrule.setWeekStart(fromWeekDay(defaultRR->weekStart())); rrule.setFrequency(fromRecurrenceType(defaultRR->recurrenceType())); rrule.setInterval(defaultRR->frequency()); if (defaultRR->duration() != 0) { //Inidcates if end date is set or not if (defaultRR->duration() > 0) { rrule.setCount(defaultRR->duration()); } } else { rrule.setEnd(fromDate(defaultRR->endDt(), e.allDay())); } const QVector bySecondsVector = defaultRR->bySeconds().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setBysecond(bySecondsVector.toStdVector()); -#else const auto stdVectorBySeconds = std::vector(bySecondsVector.begin(), bySecondsVector.end()); rrule.setBysecond(stdVectorBySeconds); -#endif const QVector byMinutesVector = defaultRR->byMinutes().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setByminute(byMinutesVector.toStdVector()); -#else const auto stdVectorByMinutes = std::vector(byMinutesVector.begin(), byMinutesVector.end()); rrule.setByminute(stdVectorByMinutes); -#endif const QVector byHoursVector = defaultRR->byHours().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setByhour(byHoursVector.toStdVector()); -#else const auto stdVectorByHours = std::vector(byHoursVector.begin(), byHoursVector.end()); rrule.setByhour(stdVectorByHours); -#endif std::vector daypos; daypos.reserve(defaultRR->byDays().count()); foreach (const KCalendarCore::RecurrenceRule::WDayPos &dp, defaultRR->byDays()) { daypos.push_back(fromWeekDayPos(dp)); } rrule.setByday(daypos); const QVector bymonthdayVector = defaultRR->byMonthDays().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setBymonthday(bymonthdayVector.toStdVector()); -#else const auto stdByMonthDayVector = std::vector(bymonthdayVector.begin(), bymonthdayVector.end()); rrule.setBymonthday(stdByMonthDayVector); -#endif const QVector byYearDaysVector = defaultRR->byYearDays().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setByyearday(byYearDaysVector.toStdVector()); -#else const auto stdByYearDayVector = std::vector(byYearDaysVector.begin(), byYearDaysVector.end()); rrule.setByyearday(stdByYearDayVector); -#endif const QVector byWeekNumberVector = defaultRR->byWeekNumbers().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setByweekno(byWeekNumberVector.toStdVector()); -#else const auto stdWeekNumberVector = std::vector(byWeekNumberVector.begin(), byWeekNumberVector.end()); rrule.setByweekno(stdWeekNumberVector); -#endif const QVector byMonthVector = defaultRR->byMonths().toVector(); -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - rrule.setBymonth(byMonthVector.toStdVector()); -#else const auto stdByMonthVector = std::vector(byMonthVector.begin(), byMonthVector.end()); rrule.setBymonth(stdByMonthVector); -#endif i.setRecurrenceRule(rrule); std::vector rdates; foreach (const QDateTime &dt, rec->rDateTimes()) { rdates.push_back(fromDate(dt, e.allDay())); } foreach (const QDate &dt, rec->rDates()) { rdates.push_back(fromDate(QDateTime(dt, {}), true)); } i.setRecurrenceDates(rdates); std::vector exdates; foreach (const QDateTime &dt, rec->exDateTimes()) { exdates.push_back(fromDate(dt, e.allDay())); } foreach (const QDate &dt, rec->exDates()) { exdates.push_back(fromDate(QDateTime(dt, {}), true)); } i.setExceptionDates(exdates); if (!rec->exRules().empty()) { qCWarning(PIMKOLAB_LOG) <<"exrules are not supported"; } } template void setTodoEvent(KCalendarCore::Incidence &i, const T &e) { i.setPriority(toPriority(e.priority())); if (!e.location().empty()) { i.setLocation(fromStdString(e.location())); //TODO detect richtext } if (e.organizer().isValid()) { i.setOrganizer(KCalendarCore::Person(fromStdString(e.organizer().name()), fromStdString(e.organizer().email()))); //TODO handle uid too } if (!e.url().empty()) { i.setNonKDECustomProperty(CUSTOM_KOLAB_URL, fromStdString(e.url())); } if (e.recurrenceID().isValid()) { i.setRecurrenceId(toDate(e.recurrenceID())); //TODO THISANDFUTURE } setRecurrence(i, e); foreach (const Kolab::Alarm &a, e.alarms()) { KCalendarCore::Alarm::Ptr alarm = KCalendarCore::Alarm::Ptr(new KCalendarCore::Alarm(&i)); switch (a.type()) { case Kolab::Alarm::EMailAlarm: { KCalendarCore::Person::List receipents; foreach (Kolab::ContactReference c, a.attendees()) { KCalendarCore::Person person(fromStdString(c.name()), fromStdString(c.email())); receipents.append(person); } alarm->setEmailAlarm(fromStdString(a.summary()), fromStdString(a.description()), receipents); break; } case Kolab::Alarm::DisplayAlarm: alarm->setDisplayAlarm(fromStdString(a.text())); break; case Kolab::Alarm::AudioAlarm: alarm->setAudioAlarm(fromStdString(a.audioFile().uri())); break; default: qCCritical(PIMKOLAB_LOG) << "invalid alarm"; } if (a.start().isValid()) { alarm->setTime(toDate(a.start())); } else if (a.relativeStart().isValid()) { if (a.relativeTo() == Kolab::End) { alarm->setEndOffset(toDuration(a.relativeStart())); } else { alarm->setStartOffset(toDuration(a.relativeStart())); } } alarm->setSnoozeTime(toDuration(a.duration())); alarm->setRepeatCount(a.numrepeat()); alarm->setEnabled(true); i.addAlarm(alarm); } } template void getTodoEvent(T &i, const I &e) { i.setPriority(fromPriority(e.priority())); i.setLocation(toStdString(e.location())); if (!e.organizer().email().isEmpty()) { i.setOrganizer(Kolab::ContactReference(Kolab::ContactReference::EmailReference, toStdString(e.organizer().email()), toStdString(e.organizer().name()))); //TODO handle uid too } i.setUrl(toStdString(e.nonKDECustomProperty(CUSTOM_KOLAB_URL))); i.setRecurrenceID(fromDate(e.recurrenceId(), e.allDay()), false); //TODO THISANDFUTURE getRecurrence(i, e); std::vector alarms; foreach (const KCalendarCore::Alarm::Ptr &a, e.alarms()) { Kolab::Alarm alarm; //TODO KCalendarCore disables alarms using KCalendarCore::Alarm::enabled() (X-KDE-KCALCORE-ENABLED) We should either delete the alarm, or store the attribute . //Ideally we would store the alarm somewhere and temporarily delete it, so we can restore it when parsing. For now we just remove disabled alarms. if (!a->enabled()) { qCWarning(PIMKOLAB_LOG) <<"skipping disabled alarm"; continue; } switch (a->type()) { case KCalendarCore::Alarm::Display: alarm = Kolab::Alarm(toStdString(a->text())); break; case KCalendarCore::Alarm::Email: { std::vector receipents; foreach (const KCalendarCore::Person &p, a->mailAddresses()) { receipents.push_back(Kolab::ContactReference(toStdString(p.email()), toStdString(p.name()))); } alarm = Kolab::Alarm(toStdString(a->mailSubject()), toStdString(a->mailText()), receipents); break; } case KCalendarCore::Alarm::Audio: { Kolab::Attachment audioFile; audioFile.setUri(toStdString(a->audioFile()), std::string()); alarm = Kolab::Alarm(audioFile); break; } default: qCCritical(PIMKOLAB_LOG) << "unhandled alarm"; } if (a->hasTime()) { alarm.setStart(fromDate(a->time(), false)); } else if (a->hasStartOffset()) { alarm.setRelativeStart(fromDuration(a->startOffset()), Kolab::Start); } else if (a->hasEndOffset()) { alarm.setRelativeStart(fromDuration(a->endOffset()), Kolab::End); } else { qCCritical(PIMKOLAB_LOG) << "alarm trigger is missing"; continue; } alarm.setDuration(fromDuration(a->snoozeTime()), a->repeatCount()); alarms.push_back(alarm); } i.setAlarms(alarms); } KCalendarCore::Event::Ptr toKCalendarCore(const Kolab::Event &event) { KCalendarCore::Event::Ptr e(new KCalendarCore::Event); setIncidence(*e, event); setTodoEvent(*e, event); if (event.end().isValid()) { e->setDtEnd(toDate(event.end())); } if (event.duration().isValid()) { e->setDuration(toDuration(event.duration())); } if (event.transparency()) { e->setTransparency(KCalendarCore::Event::Transparent); } else { e->setTransparency(KCalendarCore::Event::Opaque); } return e; } Kolab::Event fromKCalendarCore(const KCalendarCore::Event &event) { Kolab::Event e; getIncidence(e, event); getTodoEvent(e, event); if (event.hasEndDate()) { e.setEnd(fromDate(event.dtEnd(), event.allDay())); } else if (event.hasDuration()) { e.setDuration(fromDuration(event.duration())); } if (event.transparency() == KCalendarCore::Event::Transparent) { e.setTransparency(true); } else { e.setTransparency(false); } return e; } KCalendarCore::Todo::Ptr toKCalendarCore(const Kolab::Todo &todo) { KCalendarCore::Todo::Ptr e(new KCalendarCore::Todo); setIncidence(*e, todo); setTodoEvent(*e, todo); if (todo.due().isValid()) { e->setDtDue(toDate(todo.due())); } if (!todo.relatedTo().empty()) { e->setRelatedTo(Kolab::Conversion::fromStdString(todo.relatedTo().front()), KCalendarCore::Incidence::RelTypeParent); if (todo.relatedTo().size() > 1) { qCCritical(PIMKOLAB_LOG) << "only one relation support but got multiple"; } } e->setPercentComplete(todo.percentComplete()); return e; } Kolab::Todo fromKCalendarCore(const KCalendarCore::Todo &todo) { Kolab::Todo t; getIncidence(t, todo); getTodoEvent(t, todo); t.setDue(fromDate(todo.dtDue(true), todo.allDay())); t.setPercentComplete(todo.percentComplete()); const QString relatedTo = todo.relatedTo(KCalendarCore::Incidence::RelTypeParent); if (!relatedTo.isEmpty()) { std::vector relateds; relateds.push_back(Kolab::Conversion::toStdString(relatedTo)); t.setRelatedTo(relateds); } return t; } KCalendarCore::Journal::Ptr toKCalendarCore(const Kolab::Journal &journal) { KCalendarCore::Journal::Ptr e(new KCalendarCore::Journal); setIncidence(*e, journal); //TODO contacts return e; } Kolab::Journal fromKCalendarCore(const KCalendarCore::Journal &journal) { Kolab::Journal j; getIncidence(j, journal); //TODO contacts return j; } } }