diff --git a/agents/newmailnotifier/newmailnotifieragent.cpp b/agents/newmailnotifier/newmailnotifieragent.cpp index 4984cf281..645a203bb 100644 --- a/agents/newmailnotifier/newmailnotifieragent.cpp +++ b/agents/newmailnotifier/newmailnotifieragent.cpp @@ -1,481 +1,481 @@ /* Copyright (c) 2013-2020 Laurent Montel Copyright (c) 2010 Volker Krause 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 "newmailnotifieragent.h" #include #include "specialnotifierjob.h" #include "newmailnotifieradaptor.h" #include "newmailnotifieragentsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "newmailnotifier_debug.h" #include using namespace Akonadi; NewMailNotifierAgent::NewMailNotifierAgent(const QString &id) : AgentBase(id) { Kdelibs4ConfigMigrator migrate(QStringLiteral("newmailnotifieragent")); migrate.setConfigFiles(QStringList() << QStringLiteral("akonadi_newmailnotifier_agentrc") << QStringLiteral("akonadi_newmailnotifier_agent.notifyrc")); migrate.migrate(); - connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &NewMailNotifierAgent::reloadConfiguration); + connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &NewMailNotifierAgent::slotReloadConfiguration); KLocalizedString::setApplicationDomain("akonadi_newmailnotifier_agent"); Akonadi::AttributeFactory::registerAttribute(); new NewMailNotifierAdaptor(this); NewMailNotifierAgentSettings::instance(KSharedConfig::openConfig()); mIdentityManager = KIdentityManagement::IdentityManager::self(); connect(mIdentityManager, QOverload<>::of(&KIdentityManagement::IdentityManager::changed), this, &NewMailNotifierAgent::slotIdentitiesChanged); slotIdentitiesChanged(); mDefaultIconName = QStringLiteral("kmail"); QDBusConnection::sessionBus().registerObject(QStringLiteral("/NewMailNotifierAgent"), this, QDBusConnection::ExportAdaptors); QString service = QStringLiteral("org.freedesktop.Akonadi.NewMailNotifierAgent"); if (Akonadi::ServerManager::hasInstanceIdentifier()) { service += QLatin1Char('.') + Akonadi::ServerManager::instanceIdentifier(); } QDBusConnection::sessionBus().registerService(service); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceStatusChanged, this, &NewMailNotifierAgent::slotInstanceStatusChanged); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceRemoved, this, &NewMailNotifierAgent::slotInstanceRemoved); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceAdded, this, &NewMailNotifierAgent::slotInstanceAdded); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceNameChanged, this, &NewMailNotifierAgent::slotInstanceNameChanged); changeRecorder()->setMimeTypeMonitored(KMime::Message::mimeType()); changeRecorder()->itemFetchScope().setCacheOnly(true); changeRecorder()->itemFetchScope().setFetchModificationTime(false); changeRecorder()->fetchCollection(true); changeRecorder()->setChangeRecordingEnabled(false); changeRecorder()->ignoreSession(Akonadi::Session::defaultSession()); changeRecorder()->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); changeRecorder()->setCollectionMonitored(Collection::root(), true); mTimer.setInterval(5 * 1000); connect(&mTimer, &QTimer::timeout, this, &NewMailNotifierAgent::slotShowNotifications); if (isActive()) { mTimer.setSingleShot(true); } } -void NewMailNotifierAgent::reloadConfiguration() +void NewMailNotifierAgent::slotReloadConfiguration() { NewMailNotifierAgentSettings::self()->load(); } void NewMailNotifierAgent::slotIdentitiesChanged() { mListEmails = mIdentityManager->allEmails(); } void NewMailNotifierAgent::doSetOnline(bool online) { if (!online) { clearAll(); } } void NewMailNotifierAgent::setEnableAgent(bool enabled) { NewMailNotifierAgentSettings::setEnabled(enabled); NewMailNotifierAgentSettings::self()->save(); if (!enabled) { clearAll(); } } bool NewMailNotifierAgent::enabledAgent() const { return NewMailNotifierAgentSettings::enabled(); } void NewMailNotifierAgent::clearAll() { mNewMails.clear(); mInstanceNameInProgress.clear(); } bool NewMailNotifierAgent::excludeSpecialCollection(const Akonadi::Collection &collection) const { if (collection.hasAttribute()) { return true; } if (collection.hasAttribute()) { if (collection.attribute()->ignoreNewMail()) { return true; } } if (!collection.contentMimeTypes().contains(KMime::Message::mimeType())) { return true; } SpecialMailCollections::Type type = SpecialMailCollections::self()->specialCollectionType(collection); switch (type) { case SpecialMailCollections::Invalid: //Not a special collection case SpecialMailCollections::Inbox: return false; default: return true; } } void NewMailNotifierAgent::itemsRemoved(const Item::List &items) { if (!isActive()) { return; } QHash< Akonadi::Collection, QList >::iterator end(mNewMails.end()); for (QHash< Akonadi::Collection, QList >::iterator it = mNewMails.begin(); it != end; ++it) { QList idList = it.value(); bool itemFound = false; for (const Item &item : items) { const int numberOfItemsRemoved = idList.removeAll(item.id()); if (numberOfItemsRemoved > 0) { itemFound = true; } } if (itemFound) { if (mNewMails[it.key()].isEmpty()) { mNewMails.remove(it.key()); } else { mNewMails[it.key()] = idList; } } } } void NewMailNotifierAgent::itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags) { Q_UNUSED(removedFlags); if (!isActive()) { return; } for (const Akonadi::Item &item : items) { QHash< Akonadi::Collection, QList >::iterator end(mNewMails.end()); for (QHash< Akonadi::Collection, QList >::iterator it = mNewMails.begin(); it != end; ++it) { QList idList = it.value(); if (idList.contains(item.id()) && addedFlags.contains("\\SEEN")) { idList.removeAll(item.id()); if (idList.isEmpty()) { mNewMails.remove(it.key()); break; } else { (*it) = idList; } } } } } void NewMailNotifierAgent::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) { if (!isActive()) { return; } for (const Akonadi::Item &item : items) { if (ignoreStatusMail(item)) { continue; } if (excludeSpecialCollection(collectionSource)) { continue; // outbox, sent-mail, trash, drafts or templates. } if (mNewMails.contains(collectionSource)) { QList idListFrom = mNewMails[ collectionSource ]; const int removeItems = idListFrom.removeAll(item.id()); if (removeItems > 0) { if (idListFrom.isEmpty()) { mNewMails.remove(collectionSource); } else { mNewMails[ collectionSource ] = idListFrom; } if (!excludeSpecialCollection(collectionDestination)) { QList idListTo = mNewMails[ collectionDestination ]; idListTo.append(item.id()); mNewMails[ collectionDestination ] = idListTo; } } } } } bool NewMailNotifierAgent::ignoreStatusMail(const Akonadi::Item &item) { Akonadi::MessageStatus status; status.setStatusFromFlags(item.flags()); if (status.isRead() || status.isSpam() || status.isIgnored()) { return true; } return false; } void NewMailNotifierAgent::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { if (!isActive()) { return; } if (excludeSpecialCollection(collection)) { return; // outbox, sent-mail, trash, drafts or templates. } if (ignoreStatusMail(item)) { return; } if (!mTimer.isActive()) { mTimer.start(); } mNewMails[ collection ].append(item.id()); } void NewMailNotifierAgent::slotShowNotifications() { if (mNewMails.isEmpty()) { return; } if (!isActive()) { return; } if (!mInstanceNameInProgress.isEmpty()) { //Restart timer until all is done. mTimer.start(); return; } QString message; if (NewMailNotifierAgentSettings::verboseNotification()) { bool hasUniqMessage = true; Akonadi::Item::Id item = -1; QString currentPath; QStringList texts; const int numberOfCollection(mNewMails.count()); if (numberOfCollection > 1) { hasUniqMessage = false; } QHash< Akonadi::Collection, QList >::const_iterator end(mNewMails.constEnd()); for (QHash< Akonadi::Collection, QList >::const_iterator it = mNewMails.constBegin(); it != end; ++it) { const Akonadi::EntityDisplayAttribute *attr = it.key().attribute(); QString displayName; if (attr && !attr->displayName().isEmpty()) { displayName = attr->displayName(); } else { displayName = it.key().name(); } if (hasUniqMessage) { const int numberOfValue(it.value().count()); if (numberOfValue == 0) { //You can have an unique folder with 0 message return; } else if (numberOfValue == 1) { item = it.value().at(0); currentPath = displayName; break; } else { hasUniqMessage = false; } } QString resourceName; if (!mCacheResourceName.contains(it.key().resource())) { const Akonadi::AgentInstance::List lst = Akonadi::AgentManager::self()->instances(); for (const Akonadi::AgentInstance &instance : lst) { if (instance.identifier() == it.key().resource()) { mCacheResourceName.insert(instance.identifier(), instance.name()); resourceName = instance.name(); break; } } } else { resourceName = mCacheResourceName.value(it.key().resource()); } const int numberOfEmails(it.value().count()); if (numberOfEmails > 0) { texts.append(i18ncp("%2 = name of mail folder; %3 = name of Akonadi POP3/IMAP/etc resource (as user named it)", "One new email in %2 from \"%3\"", "%1 new emails in %2 from \"%3\"", numberOfEmails, displayName, resourceName)); } } if (hasUniqMessage) { SpecialNotifierJob *job = new SpecialNotifierJob(mListEmails, currentPath, item, this); job->setDefaultIconName(mDefaultIconName); connect(job, &SpecialNotifierJob::displayNotification, this, &NewMailNotifierAgent::slotDisplayNotification); connect(job, &SpecialNotifierJob::say, this, &NewMailNotifierAgent::slotSay); mNewMails.clear(); return; } else { message = texts.join(QLatin1String("
")); } } else { message = i18n("New mail arrived"); } qCDebug(NEWMAILNOTIFIER_LOG) << message; slotDisplayNotification(QPixmap(), message); mNewMails.clear(); } void NewMailNotifierAgent::slotDisplayNotification(const QPixmap &pixmap, const QString &message) { if (pixmap.isNull()) { KNotification::event(QStringLiteral("new-email"), QString(), message, mDefaultIconName, nullptr, NewMailNotifierAgentSettings::keepPersistentNotification() ? KNotification::Persistent | KNotification::SkipGrouping : KNotification::CloseOnTimeout, QStringLiteral("akonadi_newmailnotifier_agent")); } else { KNotification::event(QStringLiteral("new-email"), message, pixmap, nullptr, NewMailNotifierAgentSettings::keepPersistentNotification() ? KNotification::Persistent | KNotification::SkipGrouping : KNotification::CloseOnTimeout, QStringLiteral("akonadi_newmailnotifier_agent")); } } void NewMailNotifierAgent::slotInstanceNameChanged(const Akonadi::AgentInstance &instance) { if (!isActive()) { return; } const QString identifier(instance.identifier()); int resourceNameRemoved = mCacheResourceName.remove(identifier); if (resourceNameRemoved > 0) { mCacheResourceName.insert(identifier, instance.name()); } } void NewMailNotifierAgent::slotInstanceStatusChanged(const Akonadi::AgentInstance &instance) { if (!isActive()) { return; } const QString identifier(instance.identifier()); switch (instance.status()) { case Akonadi::AgentInstance::Broken: case Akonadi::AgentInstance::Idle: mInstanceNameInProgress.removeAll(identifier); break; case Akonadi::AgentInstance::Running: if (!excludeAgentType(instance)) { if (!mInstanceNameInProgress.contains(identifier)) { mInstanceNameInProgress.append(identifier); } } break; case Akonadi::AgentInstance::NotConfigured: //Nothing break; } } bool NewMailNotifierAgent::excludeAgentType(const Akonadi::AgentInstance &instance) { if (instance.type().mimeTypes().contains(KMime::Message::mimeType())) { const QStringList capabilities(instance.type().capabilities()); if (capabilities.contains(QLatin1String("Resource")) && !capabilities.contains(QLatin1String("Virtual")) && !capabilities.contains(QLatin1String("MailTransport"))) { return false; } else { return true; } } return true; } void NewMailNotifierAgent::slotInstanceRemoved(const Akonadi::AgentInstance &instance) { if (!isActive()) { return; } const QString identifier(instance.identifier()); mInstanceNameInProgress.removeAll(identifier); } void NewMailNotifierAgent::slotInstanceAdded(const Akonadi::AgentInstance &instance) { mCacheResourceName.insert(instance.identifier(), instance.name()); } void NewMailNotifierAgent::printDebug() { qCDebug(NEWMAILNOTIFIER_LOG) << "instance in progress: " << mInstanceNameInProgress << "\n notifier enabled : " << NewMailNotifierAgentSettings::enabled() << "\n check in progress : " << !mInstanceNameInProgress.isEmpty(); } bool NewMailNotifierAgent::isActive() const { return isOnline() && NewMailNotifierAgentSettings::enabled(); } void NewMailNotifierAgent::slotSay(const QString &message) { if (!mTextToSpeech) { mTextToSpeech = new QTextToSpeech(this); } if (mTextToSpeech->availableEngines().isEmpty()) { qCWarning(NEWMAILNOTIFIER_LOG) << "No texttospeech engine available"; } else { mTextToSpeech->say(message); } } AKONADI_AGENT_MAIN(NewMailNotifierAgent) diff --git a/agents/newmailnotifier/newmailnotifieragent.h b/agents/newmailnotifier/newmailnotifieragent.h index 7b46144e6..e91d1ad1a 100644 --- a/agents/newmailnotifier/newmailnotifieragent.h +++ b/agents/newmailnotifier/newmailnotifieragent.h @@ -1,85 +1,85 @@ /* Copyright (c) 2013-2020 Laurent Montel Copyright (c) 2010 Volker Krause 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 NEWMAILNOTIFIERAGENT_H #define NEWMAILNOTIFIERAGENT_H #include // make sure this is included before QHash, otherwise it wont find the correct qHash implementation for some reason #include #include #include #include class QTextToSpeech; namespace Akonadi { class AgentInstance; } namespace KIdentityManagement { class IdentityManager; } class NewMailNotifierAgent : public Akonadi::AgentBase, public Akonadi::AgentBase::ObserverV3 { Q_OBJECT public: explicit NewMailNotifierAgent(const QString &id); void setEnableAgent(bool b); Q_REQUIRED_RESULT bool enabledAgent() const; void printDebug(); protected: void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override; void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destinationCollection) override; void itemsRemoved(const Akonadi::Item::List &items) override; void itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags) override; void doSetOnline(bool online) override; private: void slotShowNotifications(); void slotInstanceStatusChanged(const Akonadi::AgentInstance &instance); void slotInstanceRemoved(const Akonadi::AgentInstance &instance); void slotInstanceAdded(const Akonadi::AgentInstance &instance); void slotDisplayNotification(const QPixmap &pixmap, const QString &message); void slotIdentitiesChanged(); void slotInstanceNameChanged(const Akonadi::AgentInstance &instance); void slotSay(const QString &message); bool excludeAgentType(const Akonadi::AgentInstance &instance); bool ignoreStatusMail(const Akonadi::Item &item); bool isActive() const; void clearAll(); bool excludeSpecialCollection(const Akonadi::Collection &collection) const; - void reloadConfiguration(); + void slotReloadConfiguration(); QString mDefaultIconName; QStringList mListEmails; QHash > mNewMails; QHash mCacheResourceName; QTimer mTimer; QStringList mInstanceNameInProgress; KIdentityManagement::IdentityManager *mIdentityManager = nullptr; QTextToSpeech *mTextToSpeech = nullptr; }; #endif diff --git a/resources/ews/ewsclient/auth/ewspkeyauthjob.cpp b/resources/ews/ewsclient/auth/ewspkeyauthjob.cpp index 507f28183..3952eeed0 100644 --- a/resources/ews/ewsclient/auth/ewspkeyauthjob.cpp +++ b/resources/ews/ewsclient/auth/ewspkeyauthjob.cpp @@ -1,191 +1,191 @@ /* 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 "ewspkeyauthjob.h" #include #include #include #include #include static const QMap stringToKnownCertInfoType = { {QStringLiteral("CN"), QCA::CommonName}, {QStringLiteral("L"), QCA::Locality}, {QStringLiteral("ST"), QCA::State}, {QStringLiteral("O"), QCA::Organization}, {QStringLiteral("OU"), QCA::OrganizationalUnit}, {QStringLiteral("C"), QCA::Country}, {QStringLiteral("emailAddress"), QCA::EmailLegacy} }; static QMultiMap parseCertSubjectInfo(const QString &info) { QMultiMap map; #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) for (const auto &token : info.split(QLatin1Char(','), QString::SkipEmptyParts)) { #else for (const auto &token : info.split(QLatin1Char(','), Qt::SkipEmptyParts)) { #endif const auto keyval = token.trimmed().split(QLatin1Char('=')); if (keyval.count() == 2) { if (stringToKnownCertInfoType.contains(keyval[0])) { map.insert(stringToKnownCertInfoType[keyval[0]], keyval[1]); } } } return map; } static QString escapeSlashes(const QString &str) { QString result = str; return result.replace(QLatin1Char('/'), QStringLiteral("\\/")); } EwsPKeyAuthJob::EwsPKeyAuthJob(const QUrl &pkeyUri, const QString &certFile, const QString &keyFile, const QString &keyPassword, QObject *parent) : EwsJob(parent) , mPKeyUri(pkeyUri) , mCertFile(certFile) , mKeyFile(keyFile) , mKeyPassword(keyPassword) , mNetworkAccessManager(new QNetworkAccessManager(this)) { } EwsPKeyAuthJob::~EwsPKeyAuthJob() { } void EwsPKeyAuthJob::start() { const QUrlQuery query(mPKeyUri); QMap params; for (const auto &it : query.queryItems()) { params[it.first.toLower()] = QUrl::fromPercentEncoding(it.second.toLatin1()); } - if (params.contains(QLatin1String("submiturl")) && params.contains(QLatin1String("nonce")) - && params.contains(QLatin1String("certauthorities")) && params.contains(QLatin1String("context")) - && params.contains(QLatin1String("version"))) { + if (params.contains(QStringLiteral("submiturl")) && params.contains(QStringLiteral("nonce")) + && params.contains(QStringLiteral("certauthorities")) && params.contains(QStringLiteral("context")) + && params.contains(QStringLiteral("version"))) { const auto respToken = buildAuthResponse(params); if (!respToken.isEmpty()) { sendAuthRequest(respToken, QUrl(params[QStringLiteral("submiturl")]), params[QStringLiteral("context")]); } else { emitResult(); } } else { setErrorMsg(QStringLiteral("Missing one or more input parameters")); emitResult(); } } void EwsPKeyAuthJob::sendAuthRequest(const QByteArray &respToken, const QUrl &submitUrl, const QString &context) { QNetworkRequest req(submitUrl); req.setRawHeader( "Authorization", QStringLiteral("PKeyAuth AuthToken=\"%1\",Context=\"%2\",Version=\"1.0\"").arg(QString::fromLatin1(respToken), context).toLatin1()); mAuthReply.reset(mNetworkAccessManager->get(req)); connect(mAuthReply.data(), &QNetworkReply::finished, this, &EwsPKeyAuthJob::authRequestFinished); } void EwsPKeyAuthJob::authRequestFinished() { if (mAuthReply->error() == QNetworkReply::NoError) { mResultUri = mAuthReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!mResultUri.isValid()) { setErrorMsg(QStringLiteral("Incorrect or missing redirect URI in PKeyAuth response")); } } else { setErrorMsg(QStringLiteral("Failed to process PKeyAuth request: %1").arg(mAuthReply->errorString())); } emitResult(); } QByteArray EwsPKeyAuthJob::buildAuthResponse(const QMap ¶ms) { QCA::Initializer init; if (!QCA::isSupported("cert")) { setErrorMsg(QStringLiteral("QCA was not built with PKI certificate support")); return QByteArray(); } if (params[QStringLiteral("version")] != QLatin1String("1.0")) { setErrorMsg(QStringLiteral("Unknown version of PKey Authentication: %1").arg(params[QStringLiteral("version")])); return QByteArray(); } const auto authoritiesInfo = parseCertSubjectInfo(params[QStringLiteral("certauthorities")]); QCA::ConvertResult importResult; const QCA::CertificateCollection certs = QCA::CertificateCollection::fromFlatTextFile(mCertFile, &importResult); if (importResult != QCA::ConvertGood) { setErrorMsg(QStringLiteral("Certificate import failed")); return QByteArray(); } QCA::Certificate cert; for (const auto &c : certs.certificates()) { if (c.issuerInfo() == authoritiesInfo) { cert = c; break; } } if (cert.isNull()) { setErrorMsg(QStringLiteral("No suitable certificate found")); return QByteArray(); } QCA::PrivateKey privateKey = QCA::PrivateKey::fromPEMFile(mKeyFile, mKeyPassword.toUtf8(), &importResult); if (importResult != QCA::ConvertGood) { setErrorMsg(QStringLiteral("Private key import failed")); return QByteArray(); } const QString certStr = escapeSlashes(QString::fromLatin1(cert.toDER().toBase64())); const QString header = QStringLiteral("{\"x5c\":[\"%1\"],\"typ\":\"JWT\",\"alg\":\"RS256\"}").arg(certStr); const QString payload = QStringLiteral("{\"nonce\":\"%1\",\"iat\":\"%2\",\"aud\":\"%3\"}") .arg(params[QStringLiteral("nonce")]).arg(QDateTime::currentSecsSinceEpoch()) .arg(escapeSlashes(params[QStringLiteral("submiturl")])); const auto headerB64 = header.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); const auto payloadB64 = payload.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QCA::SecureArray data(headerB64 + '.' + payloadB64); QByteArray sig = privateKey.signMessage(data, QCA::EMSA3_SHA256, QCA::IEEE_1363); return headerB64 + '.' + payloadB64 + '.' + sig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } const QUrl &EwsPKeyAuthJob::resultUri() const { return mResultUri; } diff --git a/resources/ews/ewsclient/ewsmailbox.cpp b/resources/ews/ewsclient/ewsmailbox.cpp index 982b9dbcf..35d04d12c 100644 --- a/resources/ews/ewsclient/ewsmailbox.cpp +++ b/resources/ews/ewsclient/ewsmailbox.cpp @@ -1,146 +1,146 @@ /* Copyright (C) 2015-2017 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 "ewsmailbox.h" #include #include #include "ewsclient_debug.h" class EwsMailboxPrivate : public QSharedData { public: EwsMailboxPrivate(); virtual ~EwsMailboxPrivate(); bool mValid; QString mName; QString mEmail; }; EwsMailboxPrivate::EwsMailboxPrivate() : mValid(false) { } EwsMailbox::EwsMailbox() : d(new EwsMailboxPrivate()) { } EwsMailbox::EwsMailbox(QXmlStreamReader &reader) : d(new EwsMailboxPrivate()) { while (reader.readNextStartElement()) { if (reader.namespaceUri() != ewsTypeNsUri) { qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in mailbox element:") << reader.namespaceUri(); return; } const QStringRef readerName = reader.name(); if (readerName == QLatin1String("Name")) { d->mName = reader.readElementText(); if (reader.error() != QXmlStreamReader::NoError) { qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read EWS request - invalid mailbox Name element."); return; } } else if (readerName == QLatin1String("EmailAddress")) { d->mEmail = reader.readElementText(); if (reader.error() != QXmlStreamReader::NoError) { qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read EWS request - invalid mailbox EmailAddress element."); return; } } else if (readerName == QLatin1String("RoutingType") || readerName == QLatin1String("MailboxType") || readerName == QLatin1String("ItemId")) { // Unsupported - ignore //qCWarningNC(EWSCLIENT_LOG) << QStringLiteral("Unsupported mailbox element %1").arg(reader.name().toString()); reader.skipCurrentElement(); } } d->mValid = true; } EwsMailboxPrivate::~EwsMailboxPrivate() { } EwsMailbox::EwsMailbox(const EwsMailbox &other) : d(other.d) { } EwsMailbox::EwsMailbox(EwsMailbox &&other) : d(std::move(other.d)) { } EwsMailbox::~EwsMailbox() { } EwsMailbox &EwsMailbox::operator=(const EwsMailbox &other) { d = other.d; return *this; } EwsMailbox &EwsMailbox::operator=(EwsMailbox &&other) { d = std::move(other.d); return *this; } bool EwsMailbox::isValid() const { return d->mValid; } QString EwsMailbox::name() const { return d->mName; } QString EwsMailbox::email() const { return d->mEmail; } QString EwsMailbox::emailWithName() const { if (d->mName.isEmpty()) { return d->mEmail; } else { - return QStringLiteral("%1 <%2>").arg(d->mName).arg(d->mEmail); + return QStringLiteral("%1 <%2>").arg(d->mName, d->mEmail); } } EwsMailbox::operator KMime::Types::Mailbox() const { KMime::Types::Mailbox mbox; mbox.setAddress(d->mEmail.toLatin1()); if (!d->mName.isEmpty()) { mbox.setName(d->mName); } return mbox; } diff --git a/resources/ews/test/fakeserver/fakeewsconnection.cpp b/resources/ews/test/fakeserver/fakeewsconnection.cpp index c33756413..dd76df936 100644 --- a/resources/ews/test/fakeserver/fakeewsconnection.cpp +++ b/resources/ews/test/fakeserver/fakeewsconnection.cpp @@ -1,443 +1,443 @@ /* Copyright (C) 2015-2017 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 "fakeewsconnection.h" #include #include #include #include #include #include #include #include "fakeewsserver.h" #include "fakeewsserver_debug.h" static const QHash responseCodes = { {200, QStringLiteral("OK")}, {400, QStringLiteral("Bad Request")}, {401, QStringLiteral("Unauthorized")}, {403, QStringLiteral("Forbidden")}, {404, QStringLiteral("Not Found")}, {405, QStringLiteral("Method Not Allowed")}, {500, QStringLiteral("Internal Server Error")} }; static Q_CONSTEXPR int streamingEventsHeartbeatIntervalSeconds = 5; FakeEwsConnection::FakeEwsConnection(QTcpSocket *sock, FakeEwsServer *parent) : QObject(parent) , mSock(sock) , mContentLength(0) , mKeepAlive(false) , mState(Initial) , mAuthenticated(false) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got new EWS connection."); connect(mSock.data(), &QTcpSocket::disconnected, this, &FakeEwsConnection::disconnected); connect(mSock.data(), &QTcpSocket::readyRead, this, &FakeEwsConnection::dataAvailable); connect(&mDataTimer, &QTimer::timeout, this, &FakeEwsConnection::dataTimeout); connect(&mStreamingRequestHeartbeat, &QTimer::timeout, this, &FakeEwsConnection::streamingRequestHeartbeat); connect(&mStreamingRequestTimeout, &QTimer::timeout, this, &FakeEwsConnection::streamingRequestTimeout); } FakeEwsConnection::~FakeEwsConnection() { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Connection closed."); } void FakeEwsConnection::disconnected() { deleteLater(); } void FakeEwsConnection::dataAvailable() { if (mState == Initial) { QByteArray line = mSock->readLine(); QList tokens = line.split(' '); mKeepAlive = false; if (tokens.size() < 3) { sendError(QStringLiteral("Invalid request header")); return; } if (tokens.at(0) != "POST") { sendError(QStringLiteral("Expected POST request")); return; } if (tokens.at(1) != "/EWS/Exchange.asmx") { sendError(QStringLiteral("Invalid EWS URL")); return; } mState = RequestReceived; } if (mState == RequestReceived) { QByteArray line; do { line = mSock->readLine(); if (line.toLower().startsWith(QByteArray("content-length: "))) { bool ok; mContentLength = line.trimmed().mid(16).toUInt(&ok); if (!ok) { sendError(QStringLiteral("Failed to parse content length.")); return; } } else if (line.toLower().startsWith(QByteArray("authorization: basic "))) { if (line.trimmed().mid(21) == "dGVzdDp0ZXN0") { mAuthenticated = true; } } else if (line.toLower() == "connection: keep-alive\r\n") { mKeepAlive = true; } } while (!line.trimmed().isEmpty()); if (line == "\r\n") { mState = HeadersReceived; } } if (mState == HeadersReceived) { if (mContentLength == 0) { sendError(QStringLiteral("Expected content")); return; } mContent += mSock->read(mContentLength - mContent.size()); if (mContent.size() >= static_cast(mContentLength)) { mDataTimer.stop(); if (!mAuthenticated) { QString codeStr = responseCodes.value(401); QString response(QStringLiteral("HTTP/1.1 %1 %2\r\n" "WWW-Authenticate: Basic realm=\"Fake EWS Server\"\r\n" "Connection: close\r\n" "\r\n").arg(401).arg(codeStr)); response += codeStr; mSock->write(response.toLatin1()); mSock->disconnectFromHost(); return; } FakeEwsServer::DialogEntry::HttpResponse resp = parseRequest(QString::fromUtf8(mContent)); bool chunked = false; if (resp == FakeEwsServer::EmptyResponse) { resp = handleGetEventsRequest(QString::fromUtf8(mContent)); } if (resp == FakeEwsServer::EmptyResponse) { resp = handleGetStreamingEventsRequest(QString::fromUtf8(mContent)); if (resp.second > 1000) { chunked = true; resp.second %= 1000; } } FakeEwsServer *server = qobject_cast(parent()); auto defaultReplyCallback = server->defaultReplyCallback(); if (defaultReplyCallback && (resp == FakeEwsServer::EmptyResponse)) { QXmlResultItems ri; QXmlNamePool namePool; resp = defaultReplyCallback(QString::fromUtf8(mContent), ri, namePool); qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning response from default callback ") << resp.second << QStringLiteral(": ") << resp.first; } if (resp == FakeEwsServer::EmptyResponse) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning default response 500."); resp = { QLatin1String(""), 500 }; } QByteArray buffer; QString codeStr = responseCodes.value(resp.second); QByteArray respContent = resp.first.toUtf8(); buffer += QStringLiteral("HTTP/1.1 %1 %2\r\n").arg(resp.second).arg(codeStr).toLatin1(); if (chunked) { buffer += "Transfer-Encoding: chunked\r\n"; buffer += "\r\n"; buffer += QByteArray::number(respContent.size(), 16) + "\r\n"; buffer += respContent + "\r\n"; } else { buffer += "Content-Length: " + QByteArray::number(respContent.size()) + "\r\n"; buffer += mKeepAlive ? "Connection: Keep-Alive\n" : "Connection: Close\r\n"; buffer += "\r\n"; buffer += respContent; } mSock->write(buffer); if (!mKeepAlive && !chunked) { mSock->disconnectFromHost(); } mContent.clear(); mState = Initial; } else { mDataTimer.start(3000); } } } void FakeEwsConnection::sendError(const QString &msg, ushort code) { qCWarningNC(EWSFAKE_LOG) << msg; QString codeStr = responseCodes.value(code); QByteArray response(QStringLiteral("HTTP/1.1 %1 %2\nConnection: close\n\n").arg(code).arg(codeStr).toLatin1()); response += msg.toLatin1(); mSock->write(response); mSock->disconnectFromHost(); } void FakeEwsConnection::dataTimeout() { qCWarning(EWSFAKE_LOG) << QLatin1String("Timeout waiting for content."); sendError(QStringLiteral("Timeout waiting for content.")); } FakeEwsServer::DialogEntry::HttpResponse FakeEwsConnection::parseRequest(const QString &content) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got request: ") << content; FakeEwsServer *server = qobject_cast(parent()); FakeEwsServer::DialogEntry::HttpResponse resp = FakeEwsServer::EmptyResponse; Q_FOREACH (const FakeEwsServer::DialogEntry &de, server->dialog()) { QXmlResultItems ri; QByteArray resultBytes; QString result; QBuffer resultBuffer(&resultBytes); resultBuffer.open(QIODevice::WriteOnly); QXmlQuery query; QXmlSerializer xser(query, &resultBuffer); if (!de.xQuery.isNull()) { query.setFocus(content); query.setQuery(de.xQuery); query.evaluateTo(&xser); query.evaluateTo(&ri); if (ri.hasError()) { qCDebugNC(EWSFAKE_LOG) << QStringLiteral("XQuery failed due to errors - skipping"); continue; } result = QString::fromUtf8(resultBytes); } if (!result.trimmed().isEmpty()) { qCDebugNC(EWSFAKE_LOG) << QStringLiteral("Got match for \"") << de.description << QStringLiteral("\""); if (de.replyCallback) { resp = de.replyCallback(content, ri, query.namePool()); qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning response from callback ") << resp.second << QStringLiteral(": ") << resp.first; } else { resp = {result.trimmed(), 200}; qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning response from XQuery ") << resp.second << QStringLiteral(": ") << resp.first; } break; } } if (resp == FakeEwsServer::EmptyResponse) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning empty response."); qCInfoNC(EWSFAKE_LOG) << content; } return resp; } FakeEwsServer::DialogEntry::HttpResponse FakeEwsConnection::handleGetEventsRequest(const QString &content) { const QRegularExpression re(QStringLiteral( "].*<\\w*:?SubscriptionId>(?[^<]*)<\\w*:?Watermark>(?[^<]*).*")); QRegularExpressionMatch match = re.match(content); if (!match.hasMatch() || match.hasPartialMatch()) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Not a valid GetEvents request."); return FakeEwsServer::EmptyResponse; } qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got valid GetEvents request."); QString resp = QStringLiteral("" "" "" "" "" "" "" "" "" "NoError" ""); if (match.captured(QStringLiteral("subid")).isEmpty() || match.captured(QStringLiteral("watermark")).isEmpty()) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Missing subscription id or watermark."); const QString errorResp = QStringLiteral("" "" "" "" "" "" "" "" "" "Missing subscription id or watermark." "ErrorInvalidPullSubscriptionId" "0" "" "" "" "" ""); return {errorResp, 200}; } - resp += QLatin1String("") + match.captured(QLatin1String("subid")) + QLatin1String(""); - resp += QLatin1String("") + match.captured(QLatin1String("watermark")) + QLatin1String(""); + resp += QLatin1String("") + match.captured(QStringLiteral("subid")) + QLatin1String(""); + resp += QLatin1String("") + match.captured(QStringLiteral("watermark")) + QLatin1String(""); resp += QStringLiteral("false"); FakeEwsServer *server = qobject_cast(parent()); const QStringList events = server->retrieveEventsXml(); qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning %1 events.").arg(events.size()); for (const QString &eventXml : events) { resp += eventXml; } resp += QStringLiteral("" ""); return {resp, 200}; } QString FakeEwsConnection::prepareEventsResponse(const QStringList &events) { QString resp = QStringLiteral("" "" "" "" "" "" "" "" "" "NoError" "OK"); if (!events.isEmpty()) { resp += QLatin1String("") + mStreamingSubId + QLatin1String(""); qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning %1 events.").arg(events.size()); Q_FOREACH (const QString &eventXml, events) { resp += eventXml; } resp += QStringLiteral(""); } resp += QStringLiteral("" ""); return resp; } FakeEwsServer::DialogEntry::HttpResponse FakeEwsConnection::handleGetStreamingEventsRequest(const QString &content) { const QRegularExpression re(QStringLiteral( "].*<\\w*:?SubscriptionIds><\\w*:?SubscriptionId>(?[^<]*).*<\\w*:?ConnectionTimeout>(?[^<]*).*")); QRegularExpressionMatch match = re.match(content); if (!match.hasMatch() || match.hasPartialMatch()) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Not a valid GetStreamingEvents request."); return FakeEwsServer::EmptyResponse; } qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got valid GetStreamingEvents request."); if (match.captured(QStringLiteral("subid")).isEmpty() || match.captured(QStringLiteral("timeout")).isEmpty()) { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Missing subscription id or timeout."); const QString errorResp = QStringLiteral("" "" "" "" "" "" "" "" "" "Missing subscription id or timeout." "ErrorInvalidSubscription" "0" "" "" "" "" ""); return {errorResp, 200}; } mStreamingSubId = match.captured(QStringLiteral("subid")); FakeEwsServer *server = qobject_cast(parent()); const QStringList events = server->retrieveEventsXml(); QString resp = prepareEventsResponse(events); mStreamingRequestTimeout.start(match.captured(QStringLiteral("timeout")).toInt() * 1000 * 60); mStreamingRequestHeartbeat.setSingleShot(false); mStreamingRequestHeartbeat.start(streamingEventsHeartbeatIntervalSeconds * 1000); Q_EMIT streamingRequestStarted(this); return {resp, 1200}; } void FakeEwsConnection::streamingRequestHeartbeat() { sendEvents(QStringList()); } void FakeEwsConnection::streamingRequestTimeout() { mStreamingRequestTimeout.stop(); mStreamingRequestHeartbeat.stop(); mSock->write("0\r\n\r\n"); mSock->disconnectFromHost(); } void FakeEwsConnection::sendEvents(const QStringList &events) { QByteArray resp = prepareEventsResponse(events).toUtf8(); mSock->write(QByteArray::number(resp.size(), 16) + "\r\n" + resp + "\r\n"); } diff --git a/resources/ews/test/fakeserver/fakeewsserver.cpp b/resources/ews/test/fakeserver/fakeewsserver.cpp index 013b1cdc6..fa1697211 100644 --- a/resources/ews/test/fakeserver/fakeewsserver.cpp +++ b/resources/ews/test/fakeserver/fakeewsserver.cpp @@ -1,137 +1,137 @@ /* Copyright (C) 2015-2017 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 "fakeewsserver.h" #include #include #include "fakeewsconnection.h" #include "fakeewsserver_debug.h" const FakeEwsServer::DialogEntry::HttpResponse FakeEwsServer::EmptyResponse = {QString(), 0}; FakeEwsServer::FakeEwsServer(QObject *parent) : QTcpServer(parent) , mPortNumber(0) { } FakeEwsServer::~FakeEwsServer() { qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Stopping fake EWS server."); } bool FakeEwsServer::start() { QMutexLocker lock(&mMutex); int retries = 3; bool ok; auto *generator = QRandomGenerator::global(); do { mPortNumber = (generator->bounded(10000)) + 10000; qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Starting fake EWS server at 127.0.0.1:%1").arg(mPortNumber); ok = listen(QHostAddress::LocalHost, mPortNumber); if (!ok) { qCWarningNC(EWSFAKE_LOG) << QStringLiteral("Failed to start server"); } } while (!ok && --retries); if (ok) { connect(this, &QTcpServer::newConnection, this, &FakeEwsServer::newConnectionReceived); } return ok; } void FakeEwsServer::setDialog(const DialogEntry::List &dialog) { QMutexLocker lock(&mMutex); mDialog = dialog; } -void FakeEwsServer::setDefaultReplyCallback(DialogEntry::ReplyCallback defaultReplyCallback) +void FakeEwsServer::setDefaultReplyCallback(const DialogEntry::ReplyCallback &defaultReplyCallback) { QMutexLocker lock(&mMutex); mDefaultReplyCallback = defaultReplyCallback; } void FakeEwsServer::queueEventsXml(const QStringList &events) { if (QThread::currentThread() != thread()) { qCWarningNC(EWSFAKE_LOG) << QStringLiteral("queueEventsXml called from wrong thread " "(called from ") << QThread::currentThread() << QStringLiteral(", should be ") << thread() << QStringLiteral(")"); return; } mEventQueue += events; if (mStreamingEventsConnection) { mStreamingEventsConnection->sendEvents(mEventQueue); mEventQueue.clear(); } } QStringList FakeEwsServer::retrieveEventsXml() { QStringList events = mEventQueue; mEventQueue.clear(); return events; } void FakeEwsServer::newConnectionReceived() { QTcpSocket *sock = nextPendingConnection(); FakeEwsConnection *conn = new FakeEwsConnection(sock, this); connect(conn, &FakeEwsConnection::streamingRequestStarted, this, &FakeEwsServer::streamingConnectionStarted); } const FakeEwsServer::DialogEntry::List FakeEwsServer::dialog() const { QMutexLocker lock(&mMutex); return mDialog; } const FakeEwsServer::DialogEntry::ReplyCallback FakeEwsServer::defaultReplyCallback() const { QMutexLocker lock(&mMutex); return mDefaultReplyCallback; } void FakeEwsServer::streamingConnectionStarted(FakeEwsConnection *conn) { if (mStreamingEventsConnection) { qCWarningNC(EWSFAKE_LOG) << QStringLiteral("Got new streaming connection while existing one is active - terminating existing one"); mStreamingEventsConnection->deleteLater(); } mStreamingEventsConnection = conn; } ushort FakeEwsServer::portNumber() const { QMutexLocker lock(&mMutex); return mPortNumber; } diff --git a/resources/ews/test/fakeserver/fakeewsserver.h b/resources/ews/test/fakeserver/fakeewsserver.h index 231ec3197..a528ba180 100644 --- a/resources/ews/test/fakeserver/fakeewsserver.h +++ b/resources/ews/test/fakeserver/fakeewsserver.h @@ -1,79 +1,79 @@ /* Copyright (C) 2015-2017 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 FAKEEWSSERVER_H #define FAKEEWSSERVER_H #include #include #include #include #include class FakeEwsConnection; class QXmlResultItems; class QXmlNamePool; class Q_DECL_EXPORT FakeEwsServer : public QTcpServer { Q_OBJECT public: class DialogEntry { public: typedef QPair HttpResponse; typedef std::function ReplyCallback; QString xQuery; ReplyCallback replyCallback; QString description; typedef QVector List; }; static const DialogEntry::HttpResponse EmptyResponse; explicit FakeEwsServer(QObject *parent); ~FakeEwsServer() override; bool start(); - void setDefaultReplyCallback(DialogEntry::ReplyCallback defaultReplyCallback); + void setDefaultReplyCallback(const DialogEntry::ReplyCallback &defaultReplyCallback); void queueEventsXml(const QStringList &events); void setDialog(const DialogEntry::List &dialog); ushort portNumber() const; private Q_SLOTS: void newConnectionReceived(); void streamingConnectionStarted(FakeEwsConnection *conn); private: void dataAvailable(QTcpSocket *sock); void sendError(QTcpSocket *sock, const QString &msg, ushort code = 500); const DialogEntry::List dialog() const; const DialogEntry::ReplyCallback defaultReplyCallback() const; QStringList retrieveEventsXml(); DialogEntry::List mDialog; DialogEntry::ReplyCallback mDefaultReplyCallback; QStringList mEventQueue; QPointer mStreamingEventsConnection; ushort mPortNumber; mutable QMutex mMutex; friend class FakeEwsConnection; }; #endif diff --git a/resources/ews/test/unittests/ewsoauth_ut.cpp b/resources/ews/test/unittests/ewsoauth_ut.cpp index ae6ce5930..53a612157 100644 --- a/resources/ews/test/unittests/ewsoauth_ut.cpp +++ b/resources/ews/test/unittests/ewsoauth_ut.cpp @@ -1,346 +1,346 @@ /* 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 #include #include #include "auth/ewsoauth.h" #include "ewsoauth_ut_mock.h" static const QString testEmail = QStringLiteral("joe.bloggs@unknown.com"); static const QString testClientId = QStringLiteral("b43c59cd-dd1c-41fd-bb9a-b0a1d5696a93"); static const QString testReturnUri = QStringLiteral("urn:ietf:wg:oauth:2.0:oob"); //static const QString testReturnUriPercent = QUrl::toPercentEncoding(testReturnUri); static const QString testState = QStringLiteral("joidsiuhq"); static const QString resource = QStringLiteral("https://outlook.office365.com/"); //static const QString resourcePercent = QUrl::toPercentEncoding(resource); static const QString authUrl = QStringLiteral("https://login.microsoftonline.com/common/oauth2/authorize"); static const QString tokenUrl = QStringLiteral("https://login.microsoftonline.com/common/oauth2/token"); static const QString accessToken1 = QStringLiteral("IERbOTo5NSdtY5HMntWTH1wgrRt98KmbF7nNloIdZ4SSYOU7pziJJakpHy8r6kxQi+7T9w36mWv9IWLrvEwTsA"); static const QString refreshToken1 = QStringLiteral("YW7lJFWcEISynbraq4NiLLke3rOieFdvoJEDxpjCXorJblIGM56OJSu1PZXMCQL5W3KLxS9ydxqLHxRTSdw"); static const QString idToken1 = QStringLiteral("gz7l0chu9xIi1MMgPkpHGQTmo3W7L1rQbmWAxEL5VSKHeqdIJ7E3K7vmMYTl/C1fWihB5XiLjD2GSVQoOzTfCw"); class UtEwsOAuth : public QObject { Q_OBJECT private Q_SLOTS: void initialInteractiveSuccessful(); void initialRefreshSuccessful(); void refreshSuccessful(); private: static QString formatJsonSorted(const QVariantMap &map); static int performAuthAction(EwsOAuth &oAuth, int timeout, std::function actionFn); static void setUpAccessFunction(const QString &refreshToken); static void setUpTokenFunction(const QString &accessToken, const QString &refreshToken, const QString &idToken, quint64 time, int tokenLifetime, int extTokenLifetime, QString &tokenReplyData); static void dumpEvents(const QStringList &events, const QStringList &expectedEvents); void setUpOAuth(EwsOAuth &oAuth, QStringList &events, const QString &password, const QMap &map); }; void UtEwsOAuth::initialInteractiveSuccessful() { EwsOAuth oAuth(nullptr, testEmail, testClientId, testReturnUri); QVERIFY(Mock::QWebEngineView::instance); QVERIFY(Mock::QOAuth2AuthorizationCodeFlow::instance); QStringList events; setUpOAuth(oAuth, events, QString(), QMap()); Mock::QWebEngineView::instance->setRedirectUri(Mock::QOAuth2AuthorizationCodeFlow::instance->redirectUri()); auto time = QDateTime::currentSecsSinceEpoch(); constexpr unsigned int tokenLifetime = 86399; constexpr unsigned int extTokenLifetime = 345599; QString tokenReplyData; setUpAccessFunction(refreshToken1); setUpTokenFunction(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, tokenReplyData); Mock::QOAuth2AuthorizationCodeFlow::instance->setState(testState); const auto initStatus = performAuthAction(oAuth, 1000, [](EwsOAuth *oAuth) { oAuth->init(); return true; }); QVERIFY(initStatus == 1); const auto authStatus = performAuthAction(oAuth, 2000, [](EwsOAuth *oAuth) { return oAuth->authenticate(true); }); QVERIFY(authStatus == 0); const auto authUrlString = Mock::authUrlString(authUrl, testClientId, testReturnUri, testEmail, resource, testState); const QStringList expectedEvents = { Mock::requestWalletMapString(), Mock::modifyParamsAuthString(testClientId, testReturnUri, testState), Mock::authorizeWithBrowserString(authUrlString), Mock::loadWebPageString(authUrlString), Mock::interceptRequestString(authUrlString), Mock::interceptRequestBlockedString(false), Mock::interceptRequestString(testReturnUri + QStringLiteral("?code=") + QString::fromLatin1(QUrl::toPercentEncoding(refreshToken1))), Mock::interceptRequestBlockedString(true), Mock::authorizationCallbackReceivedString(refreshToken1), Mock::modifyParamsTokenString(testClientId, testReturnUri, refreshToken1), Mock::networkReplyFinishedString(tokenReplyData), Mock::replyDataCallbackString(tokenReplyData), Mock::tokenCallbackString(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, resource) }; dumpEvents(events, expectedEvents); QVERIFY(events == expectedEvents); } void UtEwsOAuth::initialRefreshSuccessful() { EwsOAuth oAuth(nullptr, testEmail, testClientId, testReturnUri); QVERIFY(Mock::QWebEngineView::instance); QVERIFY(Mock::QOAuth2AuthorizationCodeFlow::instance); QStringList events; QMap map = { {QStringLiteral("refresh-token"), refreshToken1} }; setUpOAuth(oAuth, events, QString(), map); Mock::QWebEngineView::instance->setRedirectUri(Mock::QOAuth2AuthorizationCodeFlow::instance->redirectUri()); auto time = QDateTime::currentSecsSinceEpoch(); constexpr unsigned int tokenLifetime = 86399; constexpr unsigned int extTokenLifetime = 345599; QString tokenReplyData; setUpAccessFunction(refreshToken1); setUpTokenFunction(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, tokenReplyData); Mock::QOAuth2AuthorizationCodeFlow::instance->setState(testState); const auto initStatus = performAuthAction(oAuth, 1000, [](EwsOAuth *oAuth) { oAuth->init(); return true; }); QVERIFY(initStatus == 1); const auto authStatus = performAuthAction(oAuth, 2000, [](EwsOAuth *oAuth) { return oAuth->authenticate(true); }); QVERIFY(authStatus == 0); const auto authUrlString = Mock::authUrlString(authUrl, testClientId, testReturnUri, testEmail, resource, testState); const QStringList expectedEvents = { Mock::requestWalletMapString(), Mock::modifyParamsTokenString(testClientId, testReturnUri, refreshToken1), Mock::networkReplyFinishedString(tokenReplyData), Mock::replyDataCallbackString(tokenReplyData), Mock::tokenCallbackString(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, resource) }; dumpEvents(events, expectedEvents); QVERIFY(events == expectedEvents); } void UtEwsOAuth::refreshSuccessful() { EwsOAuth oAuth(nullptr, testEmail, testClientId, testReturnUri); QVERIFY(Mock::QWebEngineView::instance); QVERIFY(Mock::QOAuth2AuthorizationCodeFlow::instance); QStringList events; setUpOAuth(oAuth, events, QString(), QMap()); Mock::QWebEngineView::instance->setRedirectUri(Mock::QOAuth2AuthorizationCodeFlow::instance->redirectUri()); auto time = QDateTime::currentSecsSinceEpoch(); constexpr unsigned int tokenLifetime = 86399; constexpr unsigned int extTokenLifetime = 345599; QString tokenReplyData; setUpAccessFunction(refreshToken1); setUpTokenFunction(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, tokenReplyData); Mock::QOAuth2AuthorizationCodeFlow::instance->setState(testState); const auto initStatus = performAuthAction(oAuth, 1000, [](EwsOAuth *oAuth) { oAuth->init(); return true; }); QVERIFY(initStatus == 1); const auto authStatus = performAuthAction(oAuth, 2000, [](EwsOAuth *oAuth) { return oAuth->authenticate(true); }); QVERIFY(authStatus == 0); const auto authUrlString = Mock::authUrlString(authUrl, testClientId, testReturnUri, testEmail, resource, testState); const QStringList expectedEvents = { Mock::requestWalletMapString(), Mock::modifyParamsAuthString(testClientId, testReturnUri, testState), Mock::authorizeWithBrowserString(authUrlString), Mock::loadWebPageString(authUrlString), Mock::interceptRequestString(authUrlString), Mock::interceptRequestBlockedString(false), Mock::interceptRequestString(testReturnUri + QStringLiteral("?code=") + QString::fromLatin1(QUrl::toPercentEncoding(refreshToken1))), Mock::interceptRequestBlockedString(true), Mock::authorizationCallbackReceivedString(refreshToken1), Mock::modifyParamsTokenString(testClientId, testReturnUri, refreshToken1), Mock::networkReplyFinishedString(tokenReplyData), Mock::replyDataCallbackString(tokenReplyData), Mock::tokenCallbackString(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, resource) }; dumpEvents(events, expectedEvents); QVERIFY(events == expectedEvents); events.clear(); oAuth.notifyRequestAuthFailed(); const auto reauthStatus = performAuthAction(oAuth, 2000, [](EwsOAuth *oAuth) { return oAuth->authenticate(false); }); QVERIFY(reauthStatus == 0); const QStringList expectedEventsRefresh = { Mock::modifyParamsTokenString(testClientId, testReturnUri, refreshToken1), Mock::networkReplyFinishedString(tokenReplyData), Mock::replyDataCallbackString(tokenReplyData), Mock::tokenCallbackString(accessToken1, refreshToken1, idToken1, time, tokenLifetime, extTokenLifetime, resource) }; dumpEvents(events, expectedEvents); QVERIFY(events == expectedEventsRefresh); } QString UtEwsOAuth::formatJsonSorted(const QVariantMap &map) { QStringList keys = map.keys(); keys.sort(); QStringList elems; - for (const auto &key : keys) { + for (const auto &key : qAsConst(keys)) { QString val = map[key].toString(); val.replace(QLatin1Char('"'), QStringLiteral("\\\"")); elems.append(QStringLiteral("\"%1\":\"%2\"").arg(key, val)); } return QStringLiteral("{") + elems.join(QLatin1Char(',')) + QStringLiteral("}"); } int UtEwsOAuth::performAuthAction(EwsOAuth &oAuth, int timeout, std::function actionFn) { QEventLoop loop; int status = -1; QTimer timer; connect(&oAuth, &EwsOAuth::authSucceeded, &timer, [&]() { qDebug() << "succeeded"; loop.exit(0); status = 0; }); connect(&oAuth, &EwsOAuth::authFailed, &timer, [&](const QString &msg) { qDebug() << "failed" << msg; loop.exit(1); status = 1; }); connect(&timer, &QTimer::timeout, &timer, [&]() { qDebug() << "timeout"; loop.exit(1); status = 1; }); timer.setSingleShot(true); timer.start(timeout); if (!actionFn(&oAuth)) { return -1; } if (status == -1) { status = loop.exec(); } return status; } void UtEwsOAuth::setUpAccessFunction(const QString &refreshToken) { Mock::QWebEngineView::instance->setAuthFunction([&](const QUrl &, QVariantMap &map){ map[QStringLiteral("code")] = QUrl::toPercentEncoding(refreshToken); }); } void UtEwsOAuth::setUpTokenFunction(const QString &accessToken, const QString &refreshToken, const QString &idToken, quint64 time, int tokenLifetime, int extTokenLifetime, QString &tokenReplyData) { Mock::QOAuth2AuthorizationCodeFlow::instance->setTokenFunction( [=, &tokenReplyData](QString &data, QMap &headers) { QVariantMap map; map[QStringLiteral("token_type")] = QStringLiteral("Bearer"); map[QStringLiteral("scope")] = QStringLiteral("ReadWrite.All"); map[QStringLiteral("expires_in")] = QString::number(tokenLifetime); map[QStringLiteral("ext_expires_in")] = QString::number(extTokenLifetime); map[QStringLiteral("expires_on")] = QString::number(time + tokenLifetime); map[QStringLiteral("not_before")] = QString::number(time); map[QStringLiteral("resource")] = resource; map[QStringLiteral("access_token")] = accessToken; map[QStringLiteral("refresh_token")] = refreshToken; map[QStringLiteral("foci")] = QStringLiteral("1"); map[QStringLiteral("id_token")] = idToken; tokenReplyData = formatJsonSorted(map); data = tokenReplyData; headers[Mock::QNetworkRequest::ContentTypeHeader] = QStringLiteral("application/json; charset=utf-8"); return Mock::QNetworkReply::NoError; }); } void UtEwsOAuth::dumpEvents(const QStringList &events, const QStringList &expectedEvents) { for (const auto &event : events) { qDebug() << "Got event:" << event; } if (events != expectedEvents) { for (const auto &event : expectedEvents) { qDebug() << "Expected event:" << event; } } } void UtEwsOAuth::setUpOAuth(EwsOAuth &oAuth, QStringList &events, const QString &password, const QMap &map) { connect(Mock::QWebEngineView::instance.data(), &Mock::QWebEngineView::logEvent, this, [&events](const QString &event) { events.append(event); }); connect(Mock::QOAuth2AuthorizationCodeFlow::instance.data(), &Mock::QOAuth2AuthorizationCodeFlow::logEvent, this, [&events](const QString &event) { events.append(event); }); connect(&oAuth, &EwsOAuth::requestWalletPassword, this, [&oAuth, &events, password](bool) { events.append(QStringLiteral("RequestWalletPassword")); oAuth.walletPasswordRequestFinished(password); }); connect(&oAuth, &EwsOAuth::requestWalletMap, this, [&oAuth, &events, map]() { events.append(QStringLiteral("RequestWalletMap")); oAuth.walletMapRequestFinished(map); }); } QTEST_MAIN(UtEwsOAuth) #include "ewsoauth_ut.moc" diff --git a/resources/openxchange/oxa/davutils.cpp b/resources/openxchange/oxa/davutils.cpp index a675582fb..f6643d0d6 100644 --- a/resources/openxchange/oxa/davutils.cpp +++ b/resources/openxchange/oxa/davutils.cpp @@ -1,72 +1,72 @@ /* This file is part of oxaccess. Copyright (c) 2009 Tobias Koenig 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 "davutils.h" using namespace OXA; QDomElement DAVUtils::addDavElement(QDomDocument &document, QDomNode &parentNode, const QString &tag) { - const QDomElement element = document.createElementNS(QLatin1String("DAV:"), QLatin1String("D:") + tag); + const QDomElement element = document.createElementNS(QStringLiteral("DAV:"), QLatin1String("D:") + tag); parentNode.appendChild(element); return element; } QDomElement DAVUtils::addOxElement(QDomDocument &document, QDomNode &parentNode, const QString &tag, const QString &text) { - QDomElement element = document.createElementNS(QLatin1String("http://www.open-xchange.org"), QLatin1String("ox:") + tag); + QDomElement element = document.createElementNS(QStringLiteral("http://www.open-xchange.org"), QLatin1String("ox:") + tag); if (!text.isEmpty()) { const QDomText textNode = document.createTextNode(text); element.appendChild(textNode); } parentNode.appendChild(element); return element; } void DAVUtils::setOxAttribute(QDomElement &element, const QString &name, const QString &value) { element.setAttributeNS(QStringLiteral("http://www.open-xchange.org"), QStringLiteral("ox:") + name, value); } bool DAVUtils::davErrorOccurred(const QDomDocument &document, QString &errorText, QString &errorStatus) { const QDomElement documentElement = document.documentElement(); const QDomNodeList propStats = documentElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat")); for (int i = 0; i < propStats.count(); ++i) { const QDomElement propStat = propStats.at(i).toElement(); const QDomElement status = propStat.firstChildElement(QStringLiteral("status")); const QDomElement description = propStat.firstChildElement(QStringLiteral("responsedescription")); if (status.text() != QLatin1String("200")) { errorText = description.text(); errorStatus = status.text(); return true; } } return false; }