diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp index 3c4a6711907228a1a864b581382dbde7b1fed284..fad842a02d2e59a4ed0aeaaf46e18fa9b25e0806 100644 --- a/src/jamidht/conversation.cpp +++ b/src/jamidht/conversation.cpp @@ -557,6 +557,13 @@ public: gitSocketList_.erase(deviceSockets); } + /** + * Remove all git sockets and all DRT nodes associated with the given peer. + * This is used when a swarm member is banned to ensure that we stop syncing + * with them or sending them message notifications. + */ + void disconnectFromPeer(const std::string& peerUri); + std::vector<std::map<std::string, std::string>> getMembers(bool includeInvited, bool includeLeft) const; @@ -674,6 +681,24 @@ Conversation::Impl::isAdmin() const return std::filesystem::is_regular_file(fileutils::getFullPath(adminsPath, userId_ + ".crt")); } +void +Conversation::Impl::disconnectFromPeer(const std::string& peerUri) +{ + // Remove nodes from swarmManager + const auto nodes = swarmManager_->getRoutingTable().getAllNodes(); + std::vector<NodeId> toRemove; + for (const auto node : nodes) + if (peerUri == repository_->uriFromDevice(node.toString())) + toRemove.emplace_back(node); + swarmManager_->deleteNode(toRemove); + + // Remove git sockets with this member + std::vector<DeviceId> gitToRm; + for (const auto& [deviceId, _] : gitSocketList_) + if (peerUri == repository_->uriFromDevice(deviceId.toString())) + removeGitSocket(deviceId); +} + std::vector<std::map<std::string, std::string>> Conversation::Impl::getMembers(bool includeInvited, bool includeLeft, bool includeBanned) const { @@ -1370,21 +1395,7 @@ Conversation::removeMember(const std::string& contactUri, bool isDevice, const O JAMI_WARN("Vote solved for %s. %s banned", contactUri.c_str(), isDevice ? "Device" : "Member"); - - const auto nodes = sthis->pimpl_->swarmManager_->getRoutingTable().getAllNodes(); - // Remove nodes from swarmManager - std::vector<NodeId> toRemove; - for (const auto node : nodes) - if (contactUri == sthis->uriFromDevice(node.toString())) - toRemove.emplace_back(node); - sthis->pimpl_->swarmManager_->deleteNode(toRemove); - // Remove git sockets with this member - std::vector<DeviceId> gitToRm; - for (const auto& [deviceId, _] : sthis->pimpl_->gitSocketList_) - if (contactUri == sthis->uriFromDevice(deviceId.toString())) - gitToRm.emplace_back(deviceId); - for (const auto& did : gitToRm) - sthis->removeGitSocket(did); + sthis->pimpl_->disconnectFromPeer(contactUri); } sthis->pimpl_->announce(commits, true); @@ -1652,10 +1663,13 @@ Conversation::Impl::mergeHistory(const std::string& uri) JAMI_DEBUG("Successfully merge history with {:s}", uri); auto result = repository_->convCommitsToMap(newCommits); - for (const auto& commit : result) { + for (auto& commit : result) { auto it = commit.find("type"); if (it != commit.end() && it->second == "member") { repository_->refreshMembers(); + + if (commit["action"] == "ban") + disconnectFromPeer(commit["uri"]); } } return result; @@ -2269,8 +2283,10 @@ Conversation::bootstrap(std::function<void()> onBootstraped, // If it works, the callback onConnectionChanged will be called with ok=true pimpl_->bootstrapCb_ = std::move(onBootstraped); std::vector<DeviceId> devices = knownDevices; - for (const auto& m : pimpl_->repository_->devices()) - devices.insert(devices.end(), m.second.begin(), m.second.end()); + for (const auto& [member, memberDevices] : pimpl_->repository_->devices()) { + if (!isBanned(member)) + devices.insert(devices.end(), memberDevices.begin(), memberDevices.end()); + } JAMI_DEBUG("{}[SwarmManager {}] Bootstrap with {} devices", pimpl_->toString(), fmt::ptr(pimpl_->swarmManager_), diff --git a/test/unitTest/conversation/conversationMembersEvent.cpp b/test/unitTest/conversation/conversationMembersEvent.cpp index 7865729c6a25a825dbd4430398daa2020876bda7..95e5d20a07f42230bd215d610312712593f92a2f 100644 --- a/test/unitTest/conversation/conversationMembersEvent.cpp +++ b/test/unitTest/conversation/conversationMembersEvent.cpp @@ -82,7 +82,8 @@ public: void testAddAcceptOfflineThenConnects(); void testGetMembers(); void testRemoveMember(); - void testRemovedMemberDoesNotReceiveMessage(); + void testRemovedMemberDoesNotReceiveMessageFromAdmin(); + void testRemovedMemberDoesNotReceiveMessageFromPeer(); void testRemoveInvitedMember(); void testMemberBanNoBadFile(); void testMemberTryToRemoveAdmin(); @@ -135,7 +136,8 @@ private: CPPUNIT_TEST(testAddAcceptOfflineThenConnects); CPPUNIT_TEST(testGetMembers); CPPUNIT_TEST(testRemoveMember); - CPPUNIT_TEST(testRemovedMemberDoesNotReceiveMessage); + CPPUNIT_TEST(testRemovedMemberDoesNotReceiveMessageFromAdmin); + CPPUNIT_TEST(testRemovedMemberDoesNotReceiveMessageFromPeer); CPPUNIT_TEST(testRemoveInvitedMember); CPPUNIT_TEST(testMemberBanNoBadFile); CPPUNIT_TEST(testMemberTryToRemoveAdmin); @@ -698,7 +700,7 @@ ConversationMembersEventTest::testRemoveMember() } void -ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessage() +ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessageFromAdmin() { connectSignals(); @@ -712,7 +714,7 @@ ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessage() libjami::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); })); - // Now check that alice, has the only admin, can remove bob + // Now check that alice, as the only admin, can remove bob libjami::removeConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT( cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3 /* vote + ban */; })); @@ -723,6 +725,61 @@ ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessage() CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); })); } +void +ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessageFromPeer() +{ + connectSignals(); + + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); + auto bobUri = bobAccount->getUsername(); + auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); + auto carlaUri = carlaAccount->getUsername(); + Manager::instance().sendRegister(carlaId, true); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.deviceAnnounced; })); + + // Make Carla send a contact request to Bob and wait until she receives a message from + // him confirming that he has accepted the request. The point of this is to make sure + // that Bob and Carla are connected; otherwise the test below where we check that Bob + // hasn't received a message from Carla could pass for the wrong reason. + carlaAccount->addContact(bobUri); + carlaAccount->sendTrustRequest(bobUri, {}); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; })); + auto carlaMsgSize = carlaData.messages.size(); + CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(carlaUri)); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMsgSize + 1 == carlaData.messages.size(); })); + + // Alice creates a swarm and adds Bob and Carla to it + bobData.requestReceived = false; + auto convId = libjami::startConversation(aliceId); + libjami::addConversationMember(aliceId, convId, bobUri); + libjami::addConversationMember(aliceId, convId, carlaUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && carlaData.requestReceived; })); + + libjami::acceptConversationRequest(bobId, convId); + libjami::acceptConversationRequest(carlaId, convId); + auto messageReceived = [](UserData& userData, const std::string& action, const std::string& uri) { + for (auto& message : userData.messages) { + if (message.type == "member" && message.body["action"] == action && message.body["uri"] == uri) + return true; + } + return false; + }; + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(aliceData, "join", bobUri); })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(carlaData, "join", bobUri); })); + + // Alice bans Bob + libjami::removeConversationMember(aliceId, convId, bobUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(aliceData, "ban", bobUri); })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(carlaData, "ban", bobUri); })); + + // Carla's messages should now be received by Alice, but not Bob + auto aliceMsgSize = aliceData.messages.size(); + auto bobMsgSize = bobData.messages.size(); + libjami::sendMessage(carlaId, convId, "hello"s, ""); + CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobMsgSize < bobData.messages.size(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); })); +} + void ConversationMembersEventTest::testRemoveInvitedMember() {