Skip to content

Commit

Permalink
XEP 0283: Moved - Introduce QXmppMovedManager
Browse files Browse the repository at this point in the history
This allow to handle moved users.
  • Loading branch information
pasnox committed Aug 24, 2024
1 parent dc16a51 commit fb38ee3
Show file tree
Hide file tree
Showing 8 changed files with 693 additions and 13 deletions.
8 changes: 8 additions & 0 deletions doc/doap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,14 @@ SPDX-License-Identifier: CC0-1.0
<xmpp:since>1.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0283.html'/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:since>1.8</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0293.html'/>
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ set(INSTALL_HEADER_FILES
client/QXmppMessageHandler.h
client/QXmppMessageReceiptManager.h
client/QXmppMixManager.h
client/QXmppMovedManager.h
client/QXmppMucManager.h
client/QXmppOutgoingClient.h
client/QXmppRegistrationManager.h
Expand Down Expand Up @@ -269,6 +270,7 @@ set(SOURCE_FILES
client/QXmppMamManager.cpp
client/QXmppMessageReceiptManager.cpp
client/QXmppMixManager.cpp
client/QXmppMovedManager.cpp
client/QXmppMucManager.cpp
client/QXmppOutgoingClient.cpp
client/QXmppRosterManager.cpp
Expand Down
260 changes: 260 additions & 0 deletions src/client/QXmppMovedManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// SPDX-FileCopyrightText: 2024 Filipe Azevedo <[email protected]>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppMovedManager.h"

#include "QXmppConstants_p.h"
#include "QXmppDiscoveryIq.h"
#include "QXmppDiscoveryManager.h"
#include "QXmppMovedItem.h"
#include "QXmppPubSubManager.h"
#include "QXmppTask.h"
#include "QXmppUri.h"
#include "QXmppUtils.h"

#include <QUrl>

using namespace QXmpp::Private;

class QXmppMovedManagerPrivate
{
public:
QXmppPubSubManager *pubSubManager = nullptr;
QXmppDiscoveryManager *discoveryManager = nullptr;
bool supportedByServer = false;
};

///
/// \class QXmppMovedManager
///
/// This class manages user account moving as specified in \xep{0283, Moved}
///
/// In order to use this manager, make sure to add all managers needed by this manager:
/// \code
/// client->addNewExtension<QXmppDiscoveryManager>();
/// client->addNewExtension<QXmppPubSubManager>();
/// \endcode
///
/// Afterwards, you need to add this manager to the client:
/// \code
/// auto *manager = client->addNewExtension<QXmppMovedManager>();
/// \endcode
///
/// If you want to publish a moved statement use the publishStatement call with the old account:
/// \code
/// manager->publishStatement("[email protected]");
/// \endcode
///
/// Once you published your statement, you then need to subscribe to your old contacts with the new account:
/// \code
/// manager->notifyContact("[email protected]", "[email protected]", "Hey, I moved my account, please accept me.");
/// \endcode
///
/// When a contact receive a subscription request from a moved user he needs to verify the authenticity of the request.
/// The QXmppRosterManager handle it on its own if the client has the QXmppMovedManager extension available.
/// The request will be ignored entirely if the old jid incoming subscription is not part of the roster with a 'from' or 'both' type.
/// In case of the authenticity can't be established the moved element is ignored entirely. Alternatively, if the client
/// does not has QXmppMovedManager support the request message will be changed to introduce a warning message before emitting
/// the subscription{Request}Received signal.
///
/// \ingroup Managers
///
/// \since QXmpp 1.8
///

///
/// Constructs a \xep{0283, Moved} manager.
///
QXmppMovedManager::QXmppMovedManager()
: d(new QXmppMovedManagerPrivate())
{
}

QXmppMovedManager::~QXmppMovedManager() = default;

QStringList QXmppMovedManager::discoveryFeatures() const
{
return { ns_moved.toString() };
}

///
/// \property QXmppMovedManager::supportedByServer
///
/// \see QXmppMovedManager::supportedByServer()
///

///
/// Returns whether the own server supports \xep{0283, Moved} feature.
///
/// \return whether \xep{0283, Moved} feature is supported
///
bool QXmppMovedManager::supportedByServer() const
{
return d->supportedByServer;
}

///
/// \fn QXmppMovedManager::supportedByServerChanged()
///
/// Emitted when the server enabled or disabled support for \xep{0283, Moved}.
///

///
/// Publish a moved statement.
///
/// \param newBareJid JID of the new account
///
/// \return the result of the action
///
QXmppTask<QXmppClient::EmptyResult> QXmppMovedManager::publishStatement(const QString &newBareJid)
{
return chainSuccess(d->pubSubManager->publishOwnPepItem(ns_moved.toString(), QXmppMovedItem { newBareJid }), this);
}

///
/// Verify a user moved statement.
///
/// \param oldBareJid JID of the old account to check statement
/// \param newBareJid JID of the new account that send the subscription request
///
/// \return the result of the action
///
QXmppTask<QXmppClient::EmptyResult> QXmppMovedManager::verifyStatement(const QString &oldBareJid, const QString &newBareJid)
{
return chain<QXmppClient::EmptyResult>(d->pubSubManager->requestItem<QXmppMovedItem>(oldBareJid, ns_moved.toString(), QStringLiteral("current")),
this,
[=, this](QXmppPubSubManager::ItemResult<QXmppMovedItem> &&result) {
return std::visit(overloaded {
[=, this](QXmppMovedItem item) -> QXmppClient::EmptyResult {
return movedJidsMatch(newBareJid, item.newJid());
},
[=, this](QXmppError err) -> QXmppClient::EmptyResult {
// As a special case, if the attempt to retrieve the moved statement results in an error with the <gone/> condition
// as defined in RFC 6120, and that <gone/> element contains a valid XMPP URI (e.g. xmpp:[email protected]), then the
// error response MUST be handled equivalent to a <moved/> statement containing a <new-jid/> element with the JID
// provided in the URI (e.g. [email protected]).
if (err.isStanzaError()) {
const auto e = std::any_cast<QXmppStanza::Error>(err.error);
const auto newJid = [&e]() -> QString {
if (e.condition() != QXmppStanza::Error::Condition::Gone) {
return {};
}

const auto result = QXmppUri::fromString(e.redirectionUri());

if (std::holds_alternative<QXmppUri>(result)) {
return std::get<QXmppUri>(result).jid();
}

return {};
}();

if (!newJid.isEmpty()) {
return movedJidsMatch(newBareJid, newJid);
}
}

return err;
} },
std::move(result));
});
}

///
/// Notifies a contact that the user has moved to another account.
///
/// \param contactBareJid JID of the contact to send the subscription request
/// \param oldBareJid JID of the old account we moved from
/// \param sensitive If true the notification is sent sensitively
/// \param reason The reason of the move
///
/// \return the result of the action
///
QXmppTask<QXmpp::SendResult> QXmppMovedManager::notifyContact(const QString &contactBareJid, const QString &oldBareJid, bool sensitive, const QString &reason)
{
QXmppPresence packet;
packet.setTo(QXmppUtils::jidToBareJid(contactBareJid));
packet.setType(QXmppPresence::Subscribe);
packet.setStatusText(reason);
packet.setOldJid(oldBareJid);
return sensitive ? client()->sendSensitive(std::move(packet)) : client()->send(std::move(packet));
}

/// \cond
void QXmppMovedManager::onRegistered(QXmppClient *client)
{
connect(client, &QXmppClient::connected, this, [this, client]() {
if (client->streamManagementState() == QXmppClient::NewStream) {
resetCachedData();
}
});

d->discoveryManager = client->findExtension<QXmppDiscoveryManager>();
Q_ASSERT_X(d->discoveryManager, "QXmppMovedManager", "QXmppDiscoveryManager is missing");

connect(d->discoveryManager, &QXmppDiscoveryManager::infoReceived, this, &QXmppMovedManager::handleDiscoInfo);

d->pubSubManager = client->findExtension<QXmppPubSubManager>();
Q_ASSERT_X(d->pubSubManager, "QXmppMovedManager", "QXmppPubSubManager is missing");
}

void QXmppMovedManager::onUnregistered(QXmppClient *client)
{
disconnect(d->discoveryManager, &QXmppDiscoveryManager::infoReceived, this, &QXmppMovedManager::handleDiscoInfo);
resetCachedData();
disconnect(client, &QXmppClient::connected, this, nullptr);
}
/// \endcond

///
/// Handles incoming service infos specified by \xep{0030, Service Discovery}.
///
/// \param iq received Service Discovery IQ stanza
///
void QXmppMovedManager::handleDiscoInfo(const QXmppDiscoveryIq &iq)
{
// Check the server's functionality to support MOVED feature.
if (iq.from().isEmpty() || iq.from() == client()->configuration().domain()) {
// Check whether MOVED is supported.
setSupportedByServer(iq.features().contains(ns_moved));
}
}

///
/// Ensures that both JIDs match.
///
/// \param newBareJid JID of the contact that sent the subscription request
/// \param pepBareJid JID of the new account as fetched from the old account statement
///
/// \return the result of the action
///
QXmppClient::EmptyResult QXmppMovedManager::movedJidsMatch(const QString &newBareJid, const QString &pepBareJid) const
{
if (newBareJid == pepBareJid) {
return QXmpp::Success {};
}

return QXmppError { QStringLiteral("The JID does not match user statement."), {} };
}

///
/// Sets whether the own server supports \xep{0283, Moved}.
///
/// \param supportedByServer whether \xep{0283, Moved} is supported by the server
///
void QXmppMovedManager::setSupportedByServer(bool supportedByServer)
{
if (d->supportedByServer != supportedByServer) {
d->supportedByServer = supportedByServer;
Q_EMIT supportedByServerChanged();
}
}

///
/// Resets the cached data.
///
void QXmppMovedManager::resetCachedData()
{
setSupportedByServer(false);
}
50 changes: 50 additions & 0 deletions src/client/QXmppMovedManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2024 Filipe Azevedo <[email protected]>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPMOVEDMANAGER_H
#define QXMPPMOVEDMANAGER_H

#include "QXmppClient.h"
#include "QXmppClientExtension.h"

class QXmppMovedManagerPrivate;

class QXMPP_EXPORT QXmppMovedManager : public QXmppClientExtension
{
Q_OBJECT
Q_PROPERTY(bool supportedByServer READ supportedByServer NOTIFY supportedByServerChanged)

public:
explicit QXmppMovedManager();
~QXmppMovedManager() override;

QStringList discoveryFeatures() const override;

bool supportedByServer() const;
Q_SIGNAL void supportedByServerChanged();

QXmppTask<QXmppClient::EmptyResult> publishStatement(const QString &newBareJid);
QXmppTask<QXmppClient::EmptyResult> verifyStatement(const QString &oldBareJid, const QString &newBareJid);

QXmppTask<QXmpp::SendResult> notifyContact(const QString &contactBareJid, const QString &oldBareJid, bool sensitive = true, const QString &reason = {});

protected:
/// \cond
void onRegistered(QXmppClient *client) override;
void onUnregistered(QXmppClient *client) override;
/// \endcond

private:
void handleDiscoInfo(const QXmppDiscoveryIq &iq);
QXmppClient::EmptyResult movedJidsMatch(const QString &newBareJid, const QString &pepBareJid) const;

void setSupportedByServer(bool supportedByServer);
void resetCachedData();

const std::unique_ptr<QXmppMovedManagerPrivate> d;

friend class tst_QXmppMovedManager;
};

#endif // QXMPPMOVEDMANAGER_H
Loading

0 comments on commit fb38ee3

Please sign in to comment.