mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
swarm: remove a user/device from the conversation
TODO Change-Id: I23b5d00e9b69dcc667cb52543d528171f9f34fdc GitLab: #298 GitLab: #299
This commit is contained in:
@ -59,6 +59,7 @@ public:
|
||||
}
|
||||
~Impl() = default;
|
||||
|
||||
bool isAdmin() const;
|
||||
std::string repoPath() const;
|
||||
|
||||
std::unique_ptr<ConversationRepository> repository_;
|
||||
@ -73,6 +74,24 @@ public:
|
||||
std::deque<std::tuple<std::string, std::string, OnPullCb>> pullcbs_ {};
|
||||
};
|
||||
|
||||
bool
|
||||
Conversation::Impl::isAdmin() const
|
||||
{
|
||||
auto shared = account_.lock();
|
||||
if (!shared)
|
||||
return false;
|
||||
|
||||
auto adminsPath = repoPath() + DIR_SEPARATOR_STR + "admins";
|
||||
auto cert = shared->identity().second;
|
||||
auto parentCert = cert->issuer;
|
||||
if (!parentCert) {
|
||||
JAMI_ERR("Parent cert is null!");
|
||||
return false;
|
||||
}
|
||||
auto uri = parentCert->getId().toString();
|
||||
return fileutils::isFile(fileutils::getFullPath(adminsPath, uri + ".crt"));
|
||||
}
|
||||
|
||||
std::string
|
||||
Conversation::Impl::repoPath() const
|
||||
{
|
||||
@ -164,14 +183,37 @@ Conversation::id() const
|
||||
std::string
|
||||
Conversation::addMember(const std::string& contactUri)
|
||||
{
|
||||
if (isMember(contactUri, true)) {
|
||||
JAMI_WARN("Could not add member %s because it's already a member", contactUri.c_str());
|
||||
return {};
|
||||
}
|
||||
if (isBanned(contactUri)) {
|
||||
JAMI_WARN("Could not add member %s because this member is banned", contactUri.c_str());
|
||||
return {};
|
||||
}
|
||||
// Add member files and commit
|
||||
return pimpl_->repository_->addMember(contactUri);
|
||||
}
|
||||
|
||||
bool
|
||||
Conversation::removeMember(const std::string& contactUri)
|
||||
Conversation::removeMember(const std::string& contactUri, bool isDevice)
|
||||
{
|
||||
// TODO
|
||||
// Check if admin
|
||||
if (!pimpl_->isAdmin()) {
|
||||
JAMI_WARN("You're not an admin of this repo. Cannot ban %s", contactUri.c_str());
|
||||
return false;
|
||||
}
|
||||
// Vote for removal
|
||||
if (pimpl_->repository_->voteKick(contactUri, isDevice).empty()) {
|
||||
JAMI_WARN("Kicking %s failed", contactUri.c_str());
|
||||
return false;
|
||||
}
|
||||
// If admin, check vote
|
||||
if (!pimpl_->repository_->resolveVote(contactUri, isDevice).empty()) {
|
||||
JAMI_WARN("Vote solved for %s. %s banned",
|
||||
contactUri.c_str(),
|
||||
isDevice ? "Device" : "Member");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -183,12 +225,9 @@ Conversation::getMembers(bool includeInvited) const
|
||||
if (!shared)
|
||||
return result;
|
||||
|
||||
auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + shared->getAccountID()
|
||||
+ DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR
|
||||
+ pimpl_->repository_->id();
|
||||
auto adminsPath = repoPath + DIR_SEPARATOR_STR + "admins";
|
||||
auto membersPath = repoPath + DIR_SEPARATOR_STR + "members";
|
||||
auto invitedPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "invited";
|
||||
auto adminsPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "admins";
|
||||
auto membersPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "members";
|
||||
for (const auto& certificate : fileutils::readDirectory(adminsPath)) {
|
||||
if (certificate.find(".crt") == std::string::npos) {
|
||||
JAMI_WARN("Incorrect file found: %s/%s", adminsPath.c_str(), certificate.c_str());
|
||||
@ -229,18 +268,15 @@ Conversation::join()
|
||||
}
|
||||
|
||||
bool
|
||||
Conversation::isMember(const std::string& uri, bool includeInvited)
|
||||
Conversation::isMember(const std::string& uri, bool includeInvited) const
|
||||
{
|
||||
auto shared = pimpl_->account_.lock();
|
||||
if (!shared)
|
||||
return false;
|
||||
|
||||
auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + shared->getAccountID()
|
||||
+ DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR
|
||||
+ pimpl_->repository_->id();
|
||||
auto invitedPath = repoPath + DIR_SEPARATOR_STR + "invited";
|
||||
auto adminsPath = repoPath + DIR_SEPARATOR_STR + "admins";
|
||||
auto membersPath = repoPath + DIR_SEPARATOR_STR + "members";
|
||||
auto invitedPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "invited";
|
||||
auto adminsPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "admins";
|
||||
auto membersPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "members";
|
||||
std::vector<std::string> pathsToCheck = {adminsPath, membersPath};
|
||||
if (includeInvited)
|
||||
pathsToCheck.emplace_back(invitedPath);
|
||||
@ -259,6 +295,19 @@ Conversation::isMember(const std::string& uri, bool includeInvited)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
Conversation::isBanned(const std::string& uri, bool isDevice) const
|
||||
{
|
||||
auto shared = pimpl_->account_.lock();
|
||||
if (!shared)
|
||||
return true;
|
||||
|
||||
auto type = isDevice ? "devices" : "members";
|
||||
auto bannedPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR + type
|
||||
+ DIR_SEPARATOR_STR + uri + ".crt";
|
||||
return fileutils::isFile(bannedPath);
|
||||
}
|
||||
|
||||
std::string
|
||||
Conversation::sendMessage(const std::string& message,
|
||||
const std::string& type,
|
||||
|
@ -52,7 +52,7 @@ public:
|
||||
* @return Commit id or empty if fails
|
||||
*/
|
||||
std::string addMember(const std::string& contactUri);
|
||||
bool removeMember(const std::string& contactUri);
|
||||
bool removeMember(const std::string& contactUri, bool isDevice);
|
||||
/**
|
||||
* @param includeInvited If we want invited members
|
||||
* @return a vector of member details:
|
||||
@ -76,7 +76,8 @@ public:
|
||||
* @param uri URI to test
|
||||
* @return true if uri is a member
|
||||
*/
|
||||
bool isMember(const std::string& uri, bool includInvited = false);
|
||||
bool isMember(const std::string& uri, bool includeInvited = false) const;
|
||||
bool isBanned(const std::string& uri, bool isDevice = false) const;
|
||||
|
||||
// Message send
|
||||
std::string sendMessage(const std::string& message,
|
||||
|
@ -1210,7 +1210,7 @@ ConversationRepository::join()
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
return pimpl_->commit(Json::writeString(wbuilder, json));
|
||||
return commitMessage(Json::writeString(wbuilder, json));
|
||||
}
|
||||
|
||||
std::string
|
||||
@ -1281,7 +1281,7 @@ ConversationRepository::leave()
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
return pimpl_->commit(Json::writeString(wbuilder, json));
|
||||
return commitMessage(Json::writeString(wbuilder, json));
|
||||
}
|
||||
|
||||
void
|
||||
@ -1294,4 +1294,146 @@ ConversationRepository::erase()
|
||||
fileutils::removeAll(repoPath, true);
|
||||
}
|
||||
|
||||
std::string
|
||||
ConversationRepository::voteKick(const std::string& uri, bool isDevice)
|
||||
{
|
||||
// Add vote + commit
|
||||
// TODO simplify
|
||||
std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
|
||||
auto account = pimpl_->account_.lock();
|
||||
if (!account)
|
||||
return {};
|
||||
auto cert = account->identity().second;
|
||||
auto parentCert = cert->issuer;
|
||||
if (!parentCert) {
|
||||
JAMI_ERR("Parent cert is null!");
|
||||
return {};
|
||||
}
|
||||
auto adminUri = parentCert->getId().toString();
|
||||
if (adminUri == uri) {
|
||||
JAMI_WARN("Admin tried to ban theirself");
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO avoid duplicate
|
||||
auto relativeVotePath = std::string("votes") + DIR_SEPARATOR_STR
|
||||
+ (isDevice ? "devices" : "members") + DIR_SEPARATOR_STR + uri;
|
||||
auto voteDirectory = repoPath + DIR_SEPARATOR_STR + relativeVotePath;
|
||||
if (!fileutils::recursive_mkdir(voteDirectory, 0700)) {
|
||||
JAMI_ERR("Error when creating %s. Abort vote", voteDirectory.c_str());
|
||||
return {};
|
||||
}
|
||||
auto votePath = fileutils::getFullPath(voteDirectory, adminUri);
|
||||
auto voteFile = fileutils::ofstream(votePath, std::ios::trunc | std::ios::binary);
|
||||
if (!voteFile.is_open()) {
|
||||
JAMI_ERR("Could not write data to %s", votePath.c_str());
|
||||
return {};
|
||||
}
|
||||
voteFile.close();
|
||||
|
||||
auto toAdd = fileutils::getFullPath(relativeVotePath, adminUri);
|
||||
if (!pimpl_->add(toAdd.c_str()))
|
||||
return {};
|
||||
|
||||
Json::Value json;
|
||||
json["uri"] = uri;
|
||||
json["type"] = "vote";
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
return commitMessage(Json::writeString(wbuilder, json));
|
||||
}
|
||||
|
||||
std::string
|
||||
ConversationRepository::resolveVote(const std::string& uri, bool isDevice)
|
||||
{
|
||||
// Count ratio admin/votes
|
||||
auto nbAdmins = 0, nbVote = 0;
|
||||
// For each admin, check if voted
|
||||
std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
|
||||
std::string adminsPath = repoPath + "admins";
|
||||
std::string membersPath = repoPath + "members";
|
||||
std::string devicesPath = repoPath + "devices";
|
||||
std::string bannedPath = repoPath + "banned";
|
||||
auto isAdmin = fileutils::isFile(fileutils::getFullPath(adminsPath, uri + ".crt"));
|
||||
std::string type = "members";
|
||||
if (isDevice)
|
||||
type = "devices";
|
||||
else if (isAdmin)
|
||||
type = "admins";
|
||||
|
||||
auto voteDirectory = repoPath + DIR_SEPARATOR_STR + "votes" + DIR_SEPARATOR_STR
|
||||
+ (isDevice ? "devices" : "members") + DIR_SEPARATOR_STR + uri;
|
||||
for (const auto& certificate : fileutils::readDirectory(adminsPath)) {
|
||||
if (certificate.find(".crt") == std::string::npos) {
|
||||
JAMI_WARN("Incorrect file found: %s/%s", adminsPath.c_str(), certificate.c_str());
|
||||
continue;
|
||||
}
|
||||
auto adminUri = certificate.substr(0, certificate.size() - std::string(".crt").size());
|
||||
nbAdmins += 1;
|
||||
if (fileutils::isFile(fileutils::getFullPath(voteDirectory, adminUri)))
|
||||
nbVote += 1;
|
||||
}
|
||||
|
||||
if (nbAdmins > 0 && (static_cast<double>(nbVote) / static_cast<double>(nbAdmins)) > .5) {
|
||||
JAMI_WARN("More than half of the admins voted to ban %s, apply the ban", uri.c_str());
|
||||
|
||||
// Remove vote directory
|
||||
fileutils::removeAll(voteDirectory, true);
|
||||
|
||||
// Move from device or members file into banned
|
||||
std::string originFilePath = membersPath;
|
||||
if (isDevice)
|
||||
originFilePath = devicesPath;
|
||||
else if (isAdmin)
|
||||
originFilePath = adminsPath;
|
||||
originFilePath += DIR_SEPARATOR_STR + uri + ".crt";
|
||||
auto destPath = bannedPath + DIR_SEPARATOR_STR + (isDevice ? "devices" : "members");
|
||||
auto destFilePath = destPath + DIR_SEPARATOR_STR + uri + ".crt";
|
||||
if (!fileutils::recursive_mkdir(destPath, 0700)) {
|
||||
JAMI_ERR("Error when creating %s. Abort resolving vote", destPath.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (std::rename(originFilePath.c_str(), destFilePath.c_str())) {
|
||||
JAMI_ERR("Error when moving %s to %s. Abort resolving vote",
|
||||
originFilePath.c_str(),
|
||||
destFilePath.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
// If members, remove related devices
|
||||
if (!isDevice) {
|
||||
for (const auto& certificate : fileutils::readDirectory(devicesPath)) {
|
||||
auto certPath = fileutils::getFullPath(devicesPath, certificate);
|
||||
auto deviceCert = fileutils::loadTextFile(certPath);
|
||||
try {
|
||||
crypto::Certificate cert(deviceCert);
|
||||
if (auto issuer = cert.issuer)
|
||||
if (issuer->toString() == uri)
|
||||
fileutils::remove(certPath, true);
|
||||
} catch (...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if (!git_add_all(pimpl_->repository_.get()))
|
||||
return {};
|
||||
|
||||
Json::Value json;
|
||||
json["action"] = "ban";
|
||||
json["uri"] = uri;
|
||||
json["type"] = "member";
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
return commitMessage(Json::writeString(wbuilder, json));
|
||||
}
|
||||
|
||||
// If vote nok
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace jami
|
||||
|
@ -187,6 +187,9 @@ public:
|
||||
*/
|
||||
void erase();
|
||||
|
||||
std::string voteKick(const std::string& uri, bool isDevice);
|
||||
std::string resolveVote(const std::string& uri, bool isDevice);
|
||||
|
||||
private:
|
||||
ConversationRepository() = delete;
|
||||
class Impl;
|
||||
|
@ -4105,6 +4105,15 @@ JamiAccount::addConversationMember(const std::string& conversationId,
|
||||
JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (it->second->isMember(contactUri, true)) {
|
||||
JAMI_DBG("%s is already a member of %s, resend invite", contactUri.c_str(), conversationId.c_str());
|
||||
// Note: This should not be necessary, but if for whatever reason the other side didn't join
|
||||
// we should not forbid new invites
|
||||
sendTextMessage(contactUri, it->second->generateInvitation());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto commitId = it->second->addMember(contactUri);
|
||||
if (commitId.empty()) {
|
||||
JAMI_WARN("Couldn't add %s to %s", contactUri.c_str(), conversationId.c_str());
|
||||
@ -4131,11 +4140,34 @@ JamiAccount::addConversationMember(const std::string& conversationId,
|
||||
|
||||
bool
|
||||
JamiAccount::removeConversationMember(const std::string& conversationId,
|
||||
const std::string& contactUri)
|
||||
const std::string& contactUri,
|
||||
bool isDevice)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(conversationsMtx_);
|
||||
conversations_[conversationId]->removeMember(contactUri);
|
||||
return true;
|
||||
auto conversation = conversations_.find(conversationId);
|
||||
if (conversation != conversations_.end() && conversation->second) {
|
||||
auto lastCommit = conversation->second->lastCommitId();
|
||||
if (conversation->second->removeMember(contactUri, isDevice)) {
|
||||
conversation->second->loadMessages([w=weak(), lastCommit, conversationId] (auto&& messages) {
|
||||
if (auto shared = w.lock()) {
|
||||
std::reverse(messages.begin(), messages.end());
|
||||
// Announce new commits
|
||||
for (const auto& msg : messages) {
|
||||
emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(),
|
||||
conversationId,
|
||||
msg);
|
||||
}
|
||||
std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
|
||||
auto conversation = shared->conversations_.find(conversationId);
|
||||
// Send notification for others
|
||||
if (conversation != shared->conversations_.end() && conversation->second)
|
||||
shared->sendMessageNotification(*conversation->second, lastCommit, true);
|
||||
}
|
||||
}, "", lastCommit);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::map<std::string, std::string>>
|
||||
@ -4225,6 +4257,21 @@ JamiAccount::fetchNewCommits(const std::string& peer,
|
||||
std::unique_lock<std::mutex> lk(conversationsMtx_);
|
||||
auto conversation = conversations_.find(conversationId);
|
||||
if (conversation != conversations_.end() && conversation->second) {
|
||||
if (!conversation->second->isMember(peer, true)) {
|
||||
JAMI_WARN("[Account %s] %s is not a member of %s",
|
||||
getAccountID().c_str(),
|
||||
peer.c_str(),
|
||||
conversationId.c_str());
|
||||
return;
|
||||
}
|
||||
if (conversation->second->isBanned(deviceId)) {
|
||||
JAMI_WARN("[Account %s] %s is a banned device in conversation %s",
|
||||
getAccountID().c_str(),
|
||||
deviceId.c_str(),
|
||||
conversationId.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve current last message
|
||||
auto lastMessageId = conversation->second->lastCommitId();
|
||||
if (lastMessageId.empty()) {
|
||||
|
@ -518,7 +518,9 @@ public:
|
||||
bool addConversationMember(const std::string& conversationId,
|
||||
const std::string& contactUri,
|
||||
bool sendRequest = true);
|
||||
bool removeConversationMember(const std::string& conversationId, const std::string& contactUri);
|
||||
bool removeConversationMember(const std::string& conversationId,
|
||||
const std::string& contactUri,
|
||||
bool isDevice = false);
|
||||
std::vector<std::map<std::string, std::string>> getConversationMembers(
|
||||
const std::string& conversationId);
|
||||
|
||||
|
@ -52,6 +52,7 @@ public:
|
||||
|
||||
std::string aliceId;
|
||||
std::string bobId;
|
||||
std::string bob2Id;
|
||||
std::string carlaId;
|
||||
|
||||
private:
|
||||
@ -71,23 +72,43 @@ private:
|
||||
void testDeclineRequest();
|
||||
void testSendMessageToMultipleParticipants();
|
||||
void testPingPongMessages();
|
||||
void testRemoveMember();
|
||||
//void testBanDevice();
|
||||
void testMemberTryToRemoveAdmin();
|
||||
void testBannedMemberCannotSendMessage();
|
||||
// void testBannedDeviceCannotSendMessageButMemberCan();
|
||||
// void test2AdminsCannotBanEachOthers();
|
||||
void testAddBannedMember();
|
||||
// void testRevokedDeviceCannotSendMessage();
|
||||
// void test2AdminsBanMembers();
|
||||
// void test2AdminsBanOtherAdmin();
|
||||
// void testCheckAdminFakeAVoteIsDetected();
|
||||
void testAdminCannotKickTheirself();
|
||||
// void testDetectionAdminKickedHimself();
|
||||
// void testAdminRemoveConversationShouldPromoteOther();
|
||||
|
||||
CPPUNIT_TEST_SUITE(ConversationTest);
|
||||
CPPUNIT_TEST(testCreateConversation);
|
||||
CPPUNIT_TEST(testGetConversation);
|
||||
CPPUNIT_TEST(testGetConversationsAfterRm);
|
||||
CPPUNIT_TEST(testRemoveInvalidConversation);
|
||||
CPPUNIT_TEST(testRemoveConversationNoMember);
|
||||
CPPUNIT_TEST(testRemoveConversationWithMember);
|
||||
CPPUNIT_TEST(testAddMember);
|
||||
CPPUNIT_TEST(testAddOfflineMemberThenConnects);
|
||||
CPPUNIT_TEST(testGetMembers);
|
||||
CPPUNIT_TEST(testSendMessage);
|
||||
CPPUNIT_TEST(testSendMessageTriggerMessageReceived);
|
||||
CPPUNIT_TEST(testGetRequests);
|
||||
CPPUNIT_TEST(testDeclineRequest);
|
||||
CPPUNIT_TEST(testSendMessageToMultipleParticipants);
|
||||
CPPUNIT_TEST(testPingPongMessages);
|
||||
// CPPUNIT_TEST(testCreateConversation);
|
||||
// CPPUNIT_TEST(testGetConversation);
|
||||
// CPPUNIT_TEST(testGetConversationsAfterRm);
|
||||
// CPPUNIT_TEST(testRemoveInvalidConversation);
|
||||
// CPPUNIT_TEST(testRemoveConversationNoMember);
|
||||
// CPPUNIT_TEST(testRemoveConversationWithMember);
|
||||
// CPPUNIT_TEST(testAddMember);
|
||||
// CPPUNIT_TEST(testAddOfflineMemberThenConnects);
|
||||
// CPPUNIT_TEST(testGetMembers);
|
||||
// CPPUNIT_TEST(testSendMessage);
|
||||
// CPPUNIT_TEST(testSendMessageTriggerMessageReceived);
|
||||
// CPPUNIT_TEST(testGetRequests);
|
||||
// CPPUNIT_TEST(testDeclineRequest);
|
||||
// CPPUNIT_TEST(testSendMessageToMultipleParticipants);
|
||||
// CPPUNIT_TEST(testPingPongMessages);
|
||||
// CPPUNIT_TEST(testRemoveMember);
|
||||
// CPPUNIT_TEST(testBanDevice);
|
||||
// CPPUNIT_TEST(testMemberTryToRemoveAdmin);
|
||||
// CPPUNIT_TEST(testBannedMemberCannotSendMessage);
|
||||
CPPUNIT_TEST(testAddBannedMember);
|
||||
// CPPUNIT_TEST(testAdminCannotKickTheirself);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
};
|
||||
|
||||
@ -163,9 +184,15 @@ ConversationTest::tearDown()
|
||||
Manager::instance().removeAccount(aliceId, true);
|
||||
Manager::instance().removeAccount(bobId, true);
|
||||
Manager::instance().removeAccount(carlaId, true);
|
||||
if (!bob2Id.empty())
|
||||
Manager::instance().removeAccount(bob2Id, true);
|
||||
|
||||
auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
|
||||
std::remove(bobArchive.c_str());
|
||||
|
||||
// Because cppunit is not linked with dbus, just poll if removed
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
if (Manager::instance().getAccountList().size() <= currentAccSize - 3)
|
||||
if (Manager::instance().getAccountList().size() <= currentAccSize - bob2Id.empty() ? 3 : 4)
|
||||
break;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
@ -980,6 +1007,407 @@ ConversationTest::testPingPongMessages()
|
||||
DRing::unregisterSignalHandlers();
|
||||
}
|
||||
|
||||
void
|
||||
ConversationTest::testRemoveMember()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
auto aliceUri = aliceAccount->getUsername();
|
||||
auto bobUri = bobAccount->getUsername();
|
||||
auto convId = aliceAccount->startConversation();
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lk {mtx};
|
||||
std::condition_variable cv;
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
|
||||
bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
|
||||
voteMessageGenerated = false;
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
[&](const std::string& /*accountId*/,
|
||||
const std::string& /* conversationId */,
|
||||
std::map<std::string, std::string> /*metadatas*/) {
|
||||
requestReceived = true;
|
||||
cv.notify_one();
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
[&](const std::string& accountId, const std::string& conversationId) {
|
||||
if (accountId == bobId && conversationId == convId) {
|
||||
conversationReady = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
std::map<std::string, std::string> message) {
|
||||
if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
|
||||
voteMessageGenerated = true;
|
||||
cv.notify_one();
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "member") {
|
||||
memberMessageGenerated = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
|
||||
memberMessageGenerated = false;
|
||||
bobAccount->acceptConversationRequest(convId);
|
||||
CPPUNIT_ASSERT(
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
|
||||
|
||||
// Now check that alice, has the only admin, can remove bob
|
||||
memberMessageGenerated = false;
|
||||
voteMessageGenerated = false;
|
||||
aliceAccount->removeConversationMember(convId, bobUri);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
|
||||
return memberMessageGenerated && voteMessageGenerated;
|
||||
}));
|
||||
auto members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 1);
|
||||
CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
|
||||
CPPUNIT_ASSERT(members[0]["role"] == "admin");
|
||||
}
|
||||
|
||||
/*void
|
||||
ConversationTest::testBanDevice()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
auto aliceUri = aliceAccount->getUsername();
|
||||
auto bobUri = bobAccount->getUsername();
|
||||
auto bobDeviceId = std::string(bobAccount->currentDeviceId());
|
||||
auto convId = aliceAccount->startConversation();
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lk {mtx};
|
||||
std::condition_variable cv;
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
|
||||
bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
|
||||
voteMessageGenerated = false, bob2GetMessage = false, bobGetMessage = false;
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
[&](const std::string& /*accountId* /,
|
||||
const std::string& /* conversationId * /,
|
||||
std::map<std::string, std::string> /*metadatas* /) {
|
||||
requestReceived = true;
|
||||
cv.notify_one();
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
[&](const std::string& accountId, const std::string& conversationId) {
|
||||
if ((accountId == bobId || accountId == bob2Id) && conversationId == convId) {
|
||||
conversationReady = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
std::map<std::string, std::string> message) {
|
||||
if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
|
||||
voteMessageGenerated = true;
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "member") {
|
||||
memberMessageGenerated = true;
|
||||
} else if (accountId == bobId) {
|
||||
bobGetMessage = true;
|
||||
} else if (accountId == bob2Id) {
|
||||
bob2GetMessage = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
|
||||
memberMessageGenerated = false;
|
||||
bobAccount->acceptConversationRequest(convId);
|
||||
CPPUNIT_ASSERT(
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
|
||||
|
||||
// Add second device for Bob
|
||||
auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
|
||||
std::remove(bobArchive.c_str());
|
||||
bobAccount->exportArchive(bobArchive);
|
||||
std::map<std::string, std::string> details = DRing::getAccountTemplate("RING");
|
||||
details[ConfProperties::TYPE] = "RING";
|
||||
details[ConfProperties::DISPLAYNAME] = "BOB2";
|
||||
details[ConfProperties::ALIAS] = "BOB2";
|
||||
details[ConfProperties::UPNP_ENABLED] = "true";
|
||||
details[ConfProperties::ARCHIVE_PASSWORD] = "";
|
||||
details[ConfProperties::ARCHIVE_PIN] = "";
|
||||
details[ConfProperties::ARCHIVE_PATH] = bobArchive;
|
||||
bob2Id = Manager::instance().addAccount(details);
|
||||
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
|
||||
[&](const std::string&, const std::map<std::string, std::string>&) {
|
||||
auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
|
||||
auto details = bob2Account->getVolatileAccountDetails();
|
||||
auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
|
||||
if (daemonStatus == "REGISTERED")
|
||||
cv.notify_one();
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
|
||||
conversationReady = false;
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady; }));
|
||||
|
||||
// Now check that alice, has the only admin, can remove bob
|
||||
memberMessageGenerated = false;
|
||||
voteMessageGenerated = false;
|
||||
auto members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 2);
|
||||
aliceAccount->removeConversationMember(convId, bobDeviceId, true);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
|
||||
return memberMessageGenerated && voteMessageGenerated;
|
||||
}));
|
||||
|
||||
auto bannedFile = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
|
||||
+ DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId
|
||||
+ DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR + "devices"
|
||||
+ DIR_SEPARATOR_STR + bobDeviceId + ".crt";
|
||||
CPPUNIT_ASSERT(fileutils::isFile(bannedFile));
|
||||
members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 2);
|
||||
|
||||
// Assert that bob2 get the message, not Bob
|
||||
CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(10), [&]() { return bobGetMessage; }));
|
||||
CPPUNIT_ASSERT(bob2GetMessage && !bobGetMessage);
|
||||
DRing::unregisterSignalHandlers();
|
||||
}*/
|
||||
|
||||
void
|
||||
ConversationTest::testMemberTryToRemoveAdmin()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
auto aliceUri = aliceAccount->getUsername();
|
||||
auto bobUri = bobAccount->getUsername();
|
||||
auto convId = aliceAccount->startConversation();
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lk {mtx};
|
||||
std::condition_variable cv;
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
|
||||
bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
[&](const std::string& /*accountId*/,
|
||||
const std::string& /* conversationId */,
|
||||
std::map<std::string, std::string> /*metadatas*/) {
|
||||
requestReceived = true;
|
||||
cv.notify_one();
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
[&](const std::string& accountId, const std::string& conversationId) {
|
||||
if (accountId == bobId && conversationId == convId) {
|
||||
conversationReady = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
std::map<std::string, std::string> message) {
|
||||
if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
|
||||
memberMessageGenerated = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
|
||||
memberMessageGenerated = false;
|
||||
bobAccount->acceptConversationRequest(convId);
|
||||
CPPUNIT_ASSERT(
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
|
||||
|
||||
// Now check that alice, has the only admin, can remove bob
|
||||
memberMessageGenerated = false;
|
||||
bobAccount->removeConversationMember(convId, aliceUri);
|
||||
auto members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 2 && !memberMessageGenerated);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationTest::testBannedMemberCannotSendMessage()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
auto aliceUri = aliceAccount->getUsername();
|
||||
auto bobUri = bobAccount->getUsername();
|
||||
auto convId = aliceAccount->startConversation();
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lk {mtx};
|
||||
std::condition_variable cv;
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
|
||||
bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
|
||||
voteMessageGenerated = false, aliceMessageReceived = false;
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
[&](const std::string& /*accountId*/,
|
||||
const std::string& /* conversationId */,
|
||||
std::map<std::string, std::string> /*metadatas*/) {
|
||||
requestReceived = true;
|
||||
cv.notify_one();
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
[&](const std::string& accountId, const std::string& conversationId) {
|
||||
if (accountId == bobId && conversationId == convId) {
|
||||
conversationReady = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
std::map<std::string, std::string> message) {
|
||||
if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
|
||||
voteMessageGenerated = true;
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "member") {
|
||||
memberMessageGenerated = true;
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "plain/text") {
|
||||
aliceMessageReceived = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
|
||||
memberMessageGenerated = false;
|
||||
bobAccount->acceptConversationRequest(convId);
|
||||
CPPUNIT_ASSERT(
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
|
||||
|
||||
memberMessageGenerated = false;
|
||||
voteMessageGenerated = false;
|
||||
aliceAccount->removeConversationMember(convId, bobUri);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
|
||||
return memberMessageGenerated && voteMessageGenerated;
|
||||
}));
|
||||
auto members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 1);
|
||||
|
||||
// Now check that alice doesn't receive a message from Bob
|
||||
aliceMessageReceived = false;
|
||||
bobAccount->sendMessage(convId, "hi");
|
||||
CPPUNIT_ASSERT(
|
||||
!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return aliceMessageReceived; }));
|
||||
}
|
||||
|
||||
void
|
||||
ConversationTest::testAddBannedMember()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
auto aliceUri = aliceAccount->getUsername();
|
||||
auto bobUri = bobAccount->getUsername();
|
||||
auto convId = aliceAccount->startConversation();
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lk {mtx};
|
||||
std::condition_variable cv;
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
|
||||
bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
|
||||
voteMessageGenerated = false;
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
[&](const std::string& /*accountId*/,
|
||||
const std::string& /* conversationId */,
|
||||
std::map<std::string, std::string> /*metadatas*/) {
|
||||
requestReceived = true;
|
||||
cv.notify_one();
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
[&](const std::string& accountId, const std::string& conversationId) {
|
||||
if (accountId == bobId && conversationId == convId) {
|
||||
conversationReady = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
std::map<std::string, std::string> message) {
|
||||
if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
|
||||
voteMessageGenerated = true;
|
||||
cv.notify_one();
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "member") {
|
||||
memberMessageGenerated = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
|
||||
memberMessageGenerated = false;
|
||||
bobAccount->acceptConversationRequest(convId);
|
||||
CPPUNIT_ASSERT(
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
|
||||
|
||||
// Now check that alice, has the only admin, can remove bob
|
||||
memberMessageGenerated = false;
|
||||
voteMessageGenerated = false;
|
||||
aliceAccount->removeConversationMember(convId, bobUri);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
|
||||
return memberMessageGenerated && voteMessageGenerated;
|
||||
}));
|
||||
|
||||
// Then check that bobUri cannot be re-added
|
||||
CPPUNIT_ASSERT(!aliceAccount->addConversationMember(convId, bobUri));
|
||||
}
|
||||
|
||||
void
|
||||
ConversationTest::testAdminCannotKickTheirself()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto aliceUri = aliceAccount->getUsername();
|
||||
auto convId = aliceAccount->startConversation();
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lk {mtx};
|
||||
std::condition_variable cv;
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
|
||||
bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
|
||||
voteMessageGenerated = false, aliceMessageReceived = false;
|
||||
confHandlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
[&](const std::string& /*accountId*/,
|
||||
const std::string& /* conversationId */,
|
||||
std::map<std::string, std::string> /*metadatas*/) {
|
||||
requestReceived = true;
|
||||
cv.notify_one();
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
[&](const std::string& accountId, const std::string& conversationId) {
|
||||
if (accountId == bobId && conversationId == convId) {
|
||||
conversationReady = true;
|
||||
cv.notify_one();
|
||||
}
|
||||
}));
|
||||
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
std::map<std::string, std::string> message) {
|
||||
if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
|
||||
voteMessageGenerated = true;
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "member") {
|
||||
memberMessageGenerated = true;
|
||||
} else if (accountId == aliceId && conversationId == convId
|
||||
&& message["type"] == "plain/text") {
|
||||
aliceMessageReceived = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
}));
|
||||
DRing::registerSignalHandlers(confHandlers);
|
||||
auto members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 1);
|
||||
aliceAccount->removeConversationMember(convId, aliceUri);
|
||||
members = aliceAccount->getConversationMembers(convId);
|
||||
CPPUNIT_ASSERT(members.size() == 1);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace jami
|
||||
|
||||
|
Reference in New Issue
Block a user