swarm: remove a user/device from the conversation

TODO

Change-Id: I23b5d00e9b69dcc667cb52543d528171f9f34fdc
GitLab: #298
GitLab: #299
This commit is contained in:
Sébastien Blin
2020-11-17 13:55:05 -05:00
parent 1169de91fb
commit ce61749408
7 changed files with 710 additions and 38 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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()) {

View File

@ -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);

View File

@ -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