Skip to content
This repository was archived by the owner on Mar 20, 2025. It is now read-only.

Commit c43cf6b

Browse files
committed
Merge branch '1.10'
2 parents ef60795 + af48bad commit c43cf6b

7 files changed

+196
-49
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ QXmpp 1.11.0 (UNRELEASED)
99

1010
*under development*
1111

12+
QXmpp 1.10.2 (March 19, 2025)
13+
-----------------------------
14+
15+
- RosterManager: Do not auto-accept Moved subscription requests to comply with XEP (@melvo, #691)
16+
1217
QXmpp 1.10.1 (February 25, 2025)
1318
--------------------------------
1419

doc/doap.xml

+7
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,13 @@ SPDX-License-Identifier: CC0-1.0
741741
<xmpp:since>1.8</xmpp:since>
742742
</xmpp:SupportedXep>
743743
</implements>
744+
<release>
745+
<Version>
746+
<revision>1.10.2</revision>
747+
<created>2025-03-19</created>
748+
<file-release rdf:resource='https://github.com/qxmpp-project/qxmpp/archive/refs/tags/v1.10.2.tar.gz'/>
749+
</Version>
750+
</release>
744751
<release>
745752
<Version>
746753
<revision>1.10.1</revision>

src/client/QXmppMovedManager.cpp

+16-19
Original file line numberDiff line numberDiff line change
@@ -253,41 +253,38 @@ void QXmppMovedManager::onUnregistered(QXmppClient *client)
253253
/// \endcond
254254

255255
///
256-
/// Checks for moved elements in incoming subscription requests and verifies them.
256+
/// Verifies an old JID in a received presence subscription request and removes it if it is invalid.
257257
///
258258
/// This requires the QXmppRosterManager to be registered with the client.
259259
///
260-
/// \returns a task for the verification result if the subscription request contains a moved
261-
/// element with an 'old-jid' that is already in the account's roster.
260+
/// \param presence presence subscription request to be processed containing a non-empty old JID.
262261
///
263-
std::optional<QXmppTask<bool>> QXmppMovedManager::handleSubscriptionRequest(const QXmppPresence &presence)
262+
/// \returns the processed presence subscription request
263+
///
264+
QXmppTask<QXmppPresence> QXmppMovedManager::processSubscriptionRequest(QXmppPresence presence)
264265
{
265-
// check for moved element
266-
if (presence.oldJid().isEmpty()) {
267-
return {};
268-
}
266+
Q_ASSERT(!presence.oldJid().isEmpty());
269267

270-
// find roster manager
271268
auto *rosterManager = client()->findExtension<QXmppRosterManager>();
272269
Q_ASSERT(rosterManager);
273270

274-
// check subscription state of old-jid
275271
const auto entry = rosterManager->getRosterEntry(presence.oldJid());
276272

277273
switch (entry.subscriptionType()) {
278274
case QXmppRosterIq::Item::From:
279275
case QXmppRosterIq::Item::Both:
280-
break;
276+
return chain<QXmppPresence>(verifyStatement(presence.oldJid(), QXmppUtils::jidToBareJid(presence.from())), this, [this, presence = presence](Result &&result) mutable {
277+
if (std::holds_alternative<QXmppError>(result)) {
278+
warning(presence.from() + u" sent a presence subscription request with the invalid old JID "_s + presence.oldJid());
279+
presence.setOldJid({});
280+
}
281+
282+
return presence;
283+
});
281284
default:
282-
// The subscription state of the old JID needs to be either from or both, else ignore
283-
// the moved element
284-
return {};
285+
presence.setOldJid({});
286+
return makeReadyTask(std::move(presence));
285287
}
286-
287-
// return verification result
288-
return chain<bool>(verifyStatement(presence.oldJid(), QXmppUtils::jidToBareJid(presence.from())), this, [this](Result &&result) mutable {
289-
return std::holds_alternative<Success>(result);
290-
});
291288
}
292289

293290
///

src/client/QXmppMovedManager.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class QXMPP_EXPORT QXmppMovedManager : public QXmppClientExtension
4141
/// \endcond
4242

4343
private:
44-
std::optional<QXmppTask<bool>> handleSubscriptionRequest(const QXmppPresence &presence);
44+
QXmppTask<QXmppPresence> processSubscriptionRequest(QXmppPresence presence);
4545
void handleDiscoInfo(const QXmppDiscoveryIq &iq);
4646
Result movedJidsMatch(const QString &newBareJid, const QString &pepBareJid) const;
4747

src/client/QXmppRosterManager.cpp

+27-26
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,16 @@ static void serializeRosterData(const RosterData &d, QXmlStreamWriter &writer)
7474
/// The user can either accept the request by calling acceptSubscription() or refuse it
7575
/// by calling refuseSubscription().
7676
///
77+
/// Since *QXmpp 1.10.2* only verified \xep{0283, Moved} old JIDs are passed in \a presence. If
78+
/// verification fails or the given old JID is not valid, the attribute is cleared in the
79+
/// QXmppPresence. See \ref rostermanager_moved "above" for more details.
80+
///
7781
/// \note If QXmppConfiguration::autoAcceptSubscriptions() is set to true or the subscription
7882
/// request is automatically accepted by the QXmppMovedManager, this signal will not be emitted.
7983
///
8084
/// \param subscriberBareJid bare JID that wants to subscribe to the user's presence
81-
/// \param presence presence stanza containing the reason / message (presence.statusText())
85+
/// \param presence presence stanza, e.g. containing the message (presence.statusText()),
86+
/// \xep{0283, Moved} old JID or other information
8287
///
8388
/// \since QXmpp 1.5
8489
///
@@ -265,42 +270,38 @@ void QXmppRosterManager::_q_presenceReceived(const QXmppPresence &presence)
265270
Q_EMIT presenceChanged(bareJid, resource);
266271
break;
267272
case QXmppPresence::Subscribe: {
268-
// accept all incoming subscription requests if enabled
269-
if (client()->configuration().autoAcceptSubscriptions()) {
270-
handleSubscriptionRequest(bareJid, presence, true);
271-
break;
272-
}
273-
274-
// check for XEP-0283: Moved subscription requests and verify them
275-
if (auto *movedManager = client()->findExtension<QXmppMovedManager>()) {
276-
if (auto verificationTask = movedManager->handleSubscriptionRequest(presence)) {
277-
verificationTask->then(this, [this, presence, bareJid](bool valid) {
278-
handleSubscriptionRequest(bareJid, presence, valid);
279-
});
280-
break;
281-
}
282-
}
283-
284-
handleSubscriptionRequest(bareJid, presence, false);
273+
handleSubscriptionRequest(bareJid, presence);
285274
break;
286275
}
287276
default:
288277
break;
289278
}
290279
}
291280

292-
void QXmppRosterManager::handleSubscriptionRequest(const QString &bareJid, const QXmppPresence &presence, bool accept)
281+
void QXmppRosterManager::handleSubscriptionRequest(const QString &bareJid, const QXmppPresence &presence)
293282
{
294-
if (accept) {
295-
// accept subscription request
296-
acceptSubscription(bareJid);
283+
auto notifyOnSubscriptionRequest = [this, bareJid](const QXmppPresence &presence) {
284+
Q_EMIT subscriptionReceived(bareJid);
285+
Q_EMIT subscriptionRequestReceived(bareJid, presence);
286+
};
297287

298-
// ask for reciprocal subscription
288+
// Automatically accept all incoming subscription requests if enabled.
289+
if (client()->configuration().autoAcceptSubscriptions()) {
290+
acceptSubscription(bareJid);
299291
subscribe(bareJid);
292+
return;
293+
}
294+
295+
// check for XEP-0283: Moved subscription requests and verify them
296+
if (auto *movedManager = client()->findExtension<QXmppMovedManager>(); movedManager && !presence.oldJid().isEmpty()) {
297+
movedManager->processSubscriptionRequest(presence).then(this, [this, notifyOnSubscriptionRequest](QXmppPresence &&presence) mutable {
298+
notifyOnSubscriptionRequest(presence);
299+
});
300300
} else {
301-
// let user decide whether to accept the subscription request
302-
Q_EMIT subscriptionReceived(bareJid);
303-
Q_EMIT subscriptionRequestReceived(bareJid, presence);
301+
auto safePresence = presence;
302+
safePresence.setOldJid({});
303+
304+
notifyOnSubscriptionRequest(safePresence);
304305
}
305306
}
306307

src/client/QXmppRosterManager.h

+28-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,33 @@ class QXmppRosterManagerPrivate;
5151
/// The \c presenceChanged() signal is emitted whenever the presence for a
5252
/// roster item changes.
5353
///
54+
/// \anchor rostermanager_moved
55+
/// ## XEP-0283: Moved
56+
///
57+
/// \xep{0283, Moved} provides a way to inform other users that an account has moved. This allows a
58+
/// presence subscription request from a new account to be automatically linked to the old account.
59+
/// However, this requires verification via a corresponding PubSub node on the old account.
60+
///
61+
/// The roster manager automatically checks entries using the QXmppMovedManager. For this to work,
62+
/// the moved manager must be added to the QXmppClient. If the moved manager is not found or the
63+
/// specified old JID is incorrect, the entry in the presence subscription request will be removed.
64+
///
65+
/// The received and verified presence subscription request is emitted via
66+
/// subscriptionRequestReceived() and the old JID can be found in QXmppPresence::oldJid().
67+
/// It is safe to assume that this old JID is valid since QXmpp 1.10.2.
68+
///
69+
/// Accepting moved subscription requests automatically is discouraged in
70+
/// [section 4.3](https://xmpp.org/extensions/xep-0283.html#receive-notification-client) of the
71+
/// XEP.
72+
///
73+
/// ### Behaviour in previous versions
74+
///
75+
/// Support in the roster manager and in QXmppPresence was initially added in 1.9.0.
76+
///
77+
/// In QXmpp 1.9.0 until 1.9.4 and 1.10.0 until 1.10.1 subscription requests with a Moved element
78+
/// are verified and automatically accepted without emitting subscriptionRequestReceived(). If
79+
/// verification fails, the **invalid** old JID is passed in subscriptionRequestReceived()!
80+
///
5481
/// \ingroup Managers
5582
///
5683
class QXMPP_EXPORT QXmppRosterManager : public QXmppClientExtension
@@ -144,7 +171,7 @@ private Q_SLOTS:
144171
private:
145172
using RosterResult = std::variant<QXmppRosterIq, QXmppError>;
146173

147-
void handleSubscriptionRequest(const QString &bareJid, const QXmppPresence &presence, bool accept);
174+
void handleSubscriptionRequest(const QString &bareJid, const QXmppPresence &presence);
148175
QXmppTask<RosterResult> requestRoster();
149176

150177
const std::unique_ptr<QXmppRosterManagerPrivate> d;

tests/qxmpprostermanager/tst_qxmpprostermanager.cpp

+112-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include "QXmppClient.h"
77
#include "QXmppDiscoveryManager.h"
8+
#include "QXmppMovedManager.h"
9+
#include "QXmppPubSubManager.h"
810
#include "QXmppRosterManager.h"
911

1012
#include "TestClient.h"
@@ -18,7 +20,9 @@ class tst_QXmppRosterManager : public QObject
1820

1921
Q_SLOT void testDiscoFeatures();
2022
Q_SLOT void testRenameItem();
21-
Q_SLOT void subscriptionRequestReceived();
23+
Q_SLOT void testSubscriptionRequestReceived();
24+
Q_SLOT void testMovedSubscriptionRequestReceived_data();
25+
Q_SLOT void testMovedSubscriptionRequestReceived();
2226
Q_SLOT void testAddItem();
2327
Q_SLOT void testRemoveItem();
2428

@@ -90,7 +94,7 @@ void tst_QXmppRosterManager::testRenameItem()
9094
QVERIFY(requestSent);
9195
}
9296

93-
void tst_QXmppRosterManager::subscriptionRequestReceived()
97+
void tst_QXmppRosterManager::testSubscriptionRequestReceived()
9498
{
9599
QXmppPresence presence;
96100
presence.setType(QXmppPresence::Subscribe);
@@ -110,6 +114,112 @@ void tst_QXmppRosterManager::subscriptionRequestReceived()
110114
QVERIFY(subscriptionRequestReceived);
111115
}
112116

117+
void tst_QXmppRosterManager::testMovedSubscriptionRequestReceived_data()
118+
{
119+
QTest::addColumn<bool>("movedManagerAdded");
120+
QTest::addColumn<QString>("oldJid");
121+
QTest::addColumn<QString>("oldJidResponse");
122+
QTest::addColumn<bool>("valid");
123+
124+
QTest::newRow("noMovedManagerNoJid")
125+
<< false
126+
<< QString()
127+
<< QString()
128+
<< false;
129+
QTest::newRow("noMovedManagerJid")
130+
<< false
131+
132+
<< QString()
133+
<< false;
134+
QTest::newRow("oldJidEmpty")
135+
<< true
136+
<< QString()
137+
<< QString()
138+
<< false;
139+
QTest::newRow("oldJidNotInRoster")
140+
<< true
141+
142+
<< QString()
143+
<< false;
144+
QTest::newRow("oldJidRespondingWithError")
145+
<< true
146+
147+
<< u"<iq id='qxmpp1' from='[email protected]' type='error'>"
148+
u"<error type='cancel'>"
149+
u"<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
150+
u"</error>"
151+
u"</iq>"_s
152+
<< false;
153+
QTest::newRow("oldJidValid")
154+
<< true
155+
156+
<< u"<iq id='qxmpp1' from='[email protected]' type='result'>"
157+
"<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
158+
"<items node='urn:xmpp:moved:1'>"
159+
"<item id='current'>"
160+
"<moved xmlns='urn:xmpp:moved:1'>"
161+
"<new-jid>[email protected]</new-jid>"
162+
"</moved>"
163+
"</item>"
164+
"</items>"
165+
"</pubsub>"
166+
"</iq>"_s
167+
<< true;
168+
}
169+
170+
void tst_QXmppRosterManager::testMovedSubscriptionRequestReceived()
171+
{
172+
TestClient client;
173+
client.configuration().setJid(u"[email protected]"_s);
174+
auto *rosterManager = client.addNewExtension<QXmppRosterManager>(&client);
175+
176+
QFETCH(bool, movedManagerAdded);
177+
QFETCH(QString, oldJid);
178+
QFETCH(QString, oldJidResponse);
179+
QFETCH(bool, valid);
180+
181+
if (movedManagerAdded) {
182+
client.addNewExtension<QXmppDiscoveryManager>();
183+
client.addNewExtension<QXmppPubSubManager>();
184+
client.addNewExtension<QXmppMovedManager>();
185+
186+
QXmppRosterIq::Item rosterItem;
187+
rosterItem.setBareJid(u"[email protected]"_s);
188+
rosterItem.setSubscriptionType(QXmppRosterIq::Item::SubscriptionType::Both);
189+
190+
QXmppRosterIq rosterIq;
191+
rosterIq.setType(QXmppIq::Set);
192+
rosterIq.setItems({ rosterItem });
193+
rosterManager->handleStanza(writePacketToDom(rosterIq));
194+
}
195+
196+
QXmppPresence presence;
197+
presence.setType(QXmppPresence::Subscribe);
198+
presence.setFrom(u"[email protected]/notebook"_s);
199+
presence.setOldJid(oldJid);
200+
201+
bool subscriptionRequestReceived = false;
202+
client.resetIdCount();
203+
204+
connect(rosterManager, &QXmppRosterManager::subscriptionRequestReceived, this, [&](const QString &subscriberBareJid, const QXmppPresence &presence) {
205+
subscriptionRequestReceived = true;
206+
QCOMPARE(subscriberBareJid, u"[email protected]"_s);
207+
if (valid && movedManagerAdded) {
208+
QCOMPARE(oldJid, presence.oldJid());
209+
} else {
210+
QVERIFY(presence.oldJid().isEmpty());
211+
}
212+
});
213+
214+
Q_EMIT client.presenceReceived(presence);
215+
216+
if (!oldJidResponse.isEmpty()) {
217+
client.inject(oldJidResponse);
218+
}
219+
220+
QVERIFY(subscriptionRequestReceived);
221+
}
222+
113223
void tst_QXmppRosterManager::testAddItem()
114224
{
115225
TestClient test;

0 commit comments

Comments
 (0)