From cb08d760f5ba06b19b18a9cb25aeec3dfafccf77 Mon Sep 17 00:00:00 2001
From: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
Date: Fri, 4 May 2018 17:30:59 -0400
Subject: [PATCH] contactmodel: Add banned contacts logic

The old lrc bannedaccountmodel suffers from many issues, both in its
design and functions. In order to be able to later remove this code,
migrate the banned contacts logic to the contactmodel.

contactmodel:

 - add a bannedContacts list with its mutex to store uris of banned
   contacts (not necessary but important for perfs).
 - modify addContact() to call directly addContact() (daemon side)
   when called with a banned contact. This will replace the old
   remove() method from the bannedAccountModel.
 - add a getBannedContacts method.
 - modify slotContactAdded to maintain the bannedContacts list and
   the old lrc. Also, when called with a banned contact, only refresh
   the filters, do not emit contactAdded signal.
 - modify slotContactRemoved to maintain the bannedContacts list.
 - modify addToContacts to maintain the bannedContacts list and
   update banned flags from the contactInfo.

bannedcontactmodel (old lrc):

 - do not update daemon when remove is called since it collides with
   the contactmodel.

   _Using the bannedcontactmodel from old LRC for maintaining banned
   contacts is now DEPRECATED and support for this functionnality is
   planned to be dropped in a near future. Please migrate to the new
   contactmodel banned contacts features._

   The recommended way to unban a contact is to call addContact. This
   is symmetric to the way we ban contacts (calling removeContact).

configuration manager mock:

 - add a synchronization mechanism to the configuration manager mock
   in order to avoid race conditions during concurrent execution of
   several removeContact / addContact functions
 - modify addContact to correctly behave if called with a banned
   contact: Instead of re-adding the contact, banned and removed
   flags should be cleared.
 - modify removeContact to use find_if instead of a for loop and
   erase contacts from the contacts list instead of simply removing
   them.

tests:

 - add a unbanContact helper.
 - fix testPlaceCallWithBannedContact to check callId instead of
   conversation size which is pointless because invariant.
 - modify all banned contacts related tests to unban and re-check.
 - add a testBanUnbanContact test.

Change-Id: I42b90559ca4baa12f0596bb927009ce6ba8b4ea3
Reviewed-by: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
---
 src/api/contactmodel.h                 |   5 +
 src/bannedcontactmodel.cpp             |   7 +-
 src/bannedcontactmodel.h               |   2 +-
 src/contactmodel.cpp                   | 104 +++++++++++++---
 test/contactmodeltester.cpp            |  47 +++++++
 test/contactmodeltester.h              |   5 +
 test/conversationmodeltester.cpp       | 163 +++++++++++++++++++------
 test/conversationmodeltester.h         |  12 +-
 test/mocks/configurationmanager_mock.h |  61 ++++++---
 9 files changed, 324 insertions(+), 82 deletions(-)

diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h
index 92b2bc2d..b0abbc32 100644
--- a/src/api/contactmodel.h
+++ b/src/api/contactmodel.h
@@ -78,6 +78,11 @@ public:
      * @throws out_of_range exception if can't find the contact
      */
     const contact::Info getContact(const std::string& contactUri) const;
+    /**
+     * get list of banned contacts.
+     * @return list of banned contacts uris as string
+     */
+    const std::list<std::string>& getBannedContacts() const;
     /**
      * @param  contactUri
      * @return empty string if no contact, else the uri in db
diff --git a/src/bannedcontactmodel.cpp b/src/bannedcontactmodel.cpp
index e4b7e881..e3683593 100644
--- a/src/bannedcontactmodel.cpp
+++ b/src/bannedcontactmodel.cpp
@@ -150,7 +150,7 @@ BannedContactModel::add(ContactMethod* cm)
  * @param cm, the ContactMethod to remove from the list.
  */
 void
-BannedContactModel::remove(ContactMethod* cm)
+BannedContactModel::remove(ContactMethod* cm, bool updatedaemon)
 {
     // Do not remove contact if contact isn't banned
     auto rowIndex = d_ptr->m_lBanned.indexOf(cm);
@@ -166,7 +166,10 @@ BannedContactModel::remove(ContactMethod* cm)
         return;
     }
 
-    ConfigurationManager::instance().addContact(cm->account()->id(), cm->uri());
+    if (updatedaemon) {
+        qWarning() << "deprecated method: updating daemon using BannedContactModel::remove is deprecated, please use ContactModel::addContact";
+        ConfigurationManager::instance().addContact(cm->account()->id(), cm->uri());
+    }
 }
 
 /**
diff --git a/src/bannedcontactmodel.h b/src/bannedcontactmodel.h
index 98143df0..9a5ca1b5 100644
--- a/src/bannedcontactmodel.h
+++ b/src/bannedcontactmodel.h
@@ -44,7 +44,7 @@ public:
 
     // Helper
     void add(ContactMethod* cm);
-    void remove(ContactMethod* cm);
+    void remove(ContactMethod* cm, bool updatedaemon = true);
     bool isBanned(ContactMethod* cm);
 
 private:
diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp
index 51e1fc0f..150f4cba 100644
--- a/src/contactmodel.cpp
+++ b/src/contactmodel.cpp
@@ -76,8 +76,9 @@ public:
      * @note: the cm must corresponds to a profile in the database.
      * @param cm ContactMethod.
      * @param type
+     * @param banned whether contact is banned or not
      */
-    void addToContacts(ContactMethod* cm, const profile::Type& type);
+    void addToContacts(ContactMethod* cm, const profile::Type& type, bool banned = false);
 
     // Helpers
     const ContactModel& linked;
@@ -86,7 +87,9 @@ public:
 
     // Containers
     ContactModel::ContactInfoMap contacts;
+    std::list<std::string> bannedContacts;
     std::mutex contactsMtx_;
+    std::mutex bannedContactsMtx_;
 
 public Q_SLOTS:
     /**
@@ -192,6 +195,15 @@ ContactModel::addContact(contact::Info contactInfo)
 {
     auto& profile = contactInfo.profileInfo;
 
+    // If passed contact is a banned contact, call the daemon to unban it
+    auto it = std::find(pimpl_->bannedContacts.begin(), pimpl_->bannedContacts.end(), profile.uri);
+    if (it != pimpl_->bannedContacts.end()) {
+        qDebug("Unban-ing contact %s", profile.uri.c_str());
+        ConfigurationManager::instance().addContact(owner.id.c_str(), profile.uri.c_str());
+        // bannedContacts will be updated in slotContactAdded
+        return;
+    }
+
     if ((owner.profileInfo.type != profile.type) and
         (profile.type == profile::Type::RING or profile.type == profile::Type::SIP)) {
             qDebug() << "ContactModel::addContact, types invalids.";
@@ -291,6 +303,12 @@ ContactModel::getContact(const std::string& contactUri) const
     return pimpl_->contacts.at(contactUri);
 }
 
+const std::list<std::string>&
+ContactModel::getBannedContacts() const
+{
+    return pimpl_->bannedContacts;
+}
+
 const std::string
 ContactModel::getContactProfileId(const std::string& contactUri) const
 {
@@ -450,7 +468,7 @@ ContactModelPimpl::fillsWithRINGContacts() {
     for (auto contact_info : contacts_vector) {
         auto cm = PhoneDirectoryModel::instance().getNumber(contact_info["id"], account);
         std::lock_guard<std::mutex> lk(contactsMtx_);
-        addToContacts(cm, linked.owner.profileInfo.type);
+        addToContacts(cm, linked.owner.profileInfo.type, contact_info["banned"] == "true");
     }
 
     // Add pending contacts
@@ -515,11 +533,41 @@ ContactModelPimpl::slotContactAdded(const std::string& accountId, const std::str
         return;
     }
     auto* cm = PhoneDirectoryModel::instance().getNumber(QString(contactUri.c_str()), account);
+    auto contact = contacts.find(contactUri);
+
+    bool isBanned = false;
+
     {
+        // Always get contactsMtx_ lock before bannedContactsMtx_.
         std::lock_guard<std::mutex> lk(contactsMtx_);
-        addToContacts(cm, linked.owner.profileInfo.type);
+
+        {
+            // Check whether contact is banned or not
+            std::lock_guard<std::mutex> lk(bannedContactsMtx_);
+            auto it = std::find(bannedContacts.begin(), bannedContacts.end(), contact->second.profileInfo.uri);
+
+            isBanned = (it != bannedContacts.end());
+
+            // If contact is banned, do not re-add it, simply update its flag and the banned contacts list
+            if (isBanned) {
+                bannedContacts.erase(it);
+
+                /* Update old LRC.
+                   This method should NOT make any function call that requires the contactsMtx_ lock
+                   otherwise we will get into a deadlock. This is only here for old-lrc transition. */
+                account->bannedContactModel()->remove(cm, false);
+            }
+
+            addToContacts(cm, linked.owner.profileInfo.type, false);
+        }
+    }
+
+    if (isBanned) {
+        // Update the smartlist
+        linked.owner.conversationModel->refreshFilter();
+    } else {
+        emit linked.contactAdded(contactUri);
     }
-    emit linked.contactAdded(contactUri);
 }
 
 void
@@ -529,6 +577,7 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s
         return;
 
     {
+        // Always get contactsMtx_ lock before bannedContactsMtx_.
         std::lock_guard<std::mutex> lk(contactsMtx_);
 
         auto contact = contacts.find(contactUri);
@@ -539,12 +588,28 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s
 
             auto* account = AccountModel::instance().getById(linked.owner.id.c_str());
             if (not account) {
-                qDebug() << "ContactModel::slotContactsAdded(), nullptr";
+                qDebug() << "ContactModel::slotContactsRemoved(), nullptr";
                 return;
             }
             auto* cm = PhoneDirectoryModel::instance().getNumber(QString(contactUri.c_str()), account);
+
+            // Update bannedContactModel from old LRC
             account->bannedContactModel()->add(cm);
+
+            // Update bannedContacts index
+            bannedContacts.emplace_back(contact->second.profileInfo.uri);
         } else {
+            if (contact->second.isBanned) {
+                // Contact was banned, update bannedContacts
+                std::lock_guard<std::mutex> lk(bannedContactsMtx_);
+                auto it = std::find(bannedContacts.begin(), bannedContacts.end(), contact->second.profileInfo.uri);
+                if (it == bannedContacts.end()) {
+                    // should not happen
+                    qDebug("ContactModel::slotContactsRemoved(): Contact is banned but not present in bannedContacts. This is most likely the result of an earlier bug.");
+                } else {
+                    bannedContacts.erase(it);
+                }
+            }
             database::removeContact(db, linked.owner.profileInfo.uri, contactUri);
             contacts.erase(contactUri);
         }
@@ -559,25 +624,22 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s
 }
 
 void
-ContactModelPimpl::addToContacts(ContactMethod* cm, const profile::Type& type)
+ContactModelPimpl::addToContacts(ContactMethod* cm, const profile::Type& type, bool banned)
 {
-    if (!cm) return;
+    if (!cm) {
+        qDebug() << "addToContacts: Called with NULL contact method.";
+    }
+
     auto contactUri = cm->uri().toStdString();
     auto contactId = database::getProfileId(db, contactUri);
     if (contactId.empty()) {
         contactId = database::getOrInsertProfile(db, contactUri, "", "",
                                                  to_string(linked.owner.profileInfo.type));
     }
+
     auto contactInfo = database::buildContactFromProfileId(db, contactId);
     contactInfo.registeredName = cm->registeredName().toStdString();
-
-    auto* account = AccountModel::instance().getById(linked.owner.id.c_str());
-    if (not account) {
-        qDebug() << "ContactModel::addToContacts(), nullptr";
-        return;
-    }
-
-    contactInfo.isBanned = account->bannedContactModel()->isBanned(cm);
+    contactInfo.isBanned = banned;
     contactInfo.isPresent = cm->isPresent();
     contactInfo.profileInfo.type = type; // Because PENDING should not be stored in the database
     auto iter = contacts.find(contactInfo.profileInfo.uri);
@@ -585,6 +647,10 @@ ContactModelPimpl::addToContacts(ContactMethod* cm, const profile::Type& type)
         iter->second = contactInfo;
     else
         contacts.emplace_hint(iter, contactInfo.profileInfo.uri, contactInfo);
+
+    if (banned) {
+        bannedContacts.emplace_back(contactUri);
+    }
 }
 
 void
@@ -668,7 +734,7 @@ ContactModelPimpl::slotIncomingCall(const std::string& fromId, const std::string
                 // The conversation model will create an entry and link the incomingCall.
                 auto* cm = PhoneDirectoryModel::instance().getNumber(QString(fromId.c_str()), account);
                 auto type = (linked.owner.profileInfo.type == profile::Type::RING) ? profile::Type::PENDING : profile::Type::SIP;
-                addToContacts(cm, type);
+                addToContacts(cm, type, false);
                 emitContactAdded = true;
             }
         }
@@ -698,7 +764,7 @@ ContactModelPimpl::slotNewAccountMessage(std::string& accountId,
             // Contact not found, load profile from database.
             // The conversation model will create an entry and link the incomingCall.
             auto* cm = PhoneDirectoryModel::instance().getNumber(QString(from.c_str()), account);
-            addToContacts(cm, profile::Type::PENDING);
+            addToContacts(cm, profile::Type::PENDING, false);
         }
     }
     emit linked.newAccountMessage(accountId, from, payloads);
@@ -710,7 +776,7 @@ ContactModelPimpl::slotNewAccountTransfer(long long dringId, datatransfer::Info
     if (info.accountId != linked.owner.id) return;
     auto* account = AccountModel::instance().getById(linked.owner.id.c_str());
     if (not account) {
-        qDebug() << "ContactModel::slotNewAccountMessage(), nullptr";
+        qDebug() << "ContactModel::slotNewAccountTransfer(), nullptr";
         return;
     }
 
@@ -720,7 +786,7 @@ ContactModelPimpl::slotNewAccountTransfer(long long dringId, datatransfer::Info
             // Contact not found, load profile from database.
             // The conversation model will create an entry and link the incomingCall.
             auto* cm = PhoneDirectoryModel::instance().getNumber(QString(info.peerUri.c_str()), account);
-            addToContacts(cm, profile::Type::PENDING);
+            addToContacts(cm, profile::Type::PENDING, false);
         }
     }
     emit linked.newAccountTransfer(dringId, info);
diff --git a/test/contactmodeltester.cpp b/test/contactmodeltester.cpp
index 83193187..958fc562 100644
--- a/test/contactmodeltester.cpp
+++ b/test/contactmodeltester.cpp
@@ -48,7 +48,54 @@ ContactModelTester::ContactModelTester()
 void
 ContactModelTester::setUp()
 {
+}
+
+void
+ContactModelTester::testBanUnbanContact()
+{
+    // "bigbadjohn" should not be in "ring1" contacts.
+    CPPUNIT_ASSERT_THROW(accInfo_.contactModel->getContact("bigbadjohn"), std::out_of_range);
+
+    // Search and add the temporaryContact
+    accInfo_.contactModel->searchContact("bigbadjohn");
+    WaitForSignalHelper(*accInfo_.contactModel,
+        SIGNAL(modelUpdated())).wait(1000);
+    auto temporaryContact = accInfo_.contactModel->getContact("");
+    std::string uri = std::string("bigbadjohn");
+    CPPUNIT_ASSERT_EQUAL(temporaryContact.profileInfo.uri, uri);
+
+    accInfo_.contactModel->addContact(temporaryContact);
+    auto contactAdded = WaitForSignalHelper(*accInfo_.contactModel,
+        SIGNAL(contactAdded(const std::string& contactUri))).wait(1000);
+    CPPUNIT_ASSERT(contactAdded);
+
+    // Ban contact
+    accInfo_.contactModel->removeContact(uri, true);
+    auto contactBanned = WaitForSignalHelper(ConfigurationManager::instance(),
+        SIGNAL(lrc::api::ConversationModel::filterChanged())).wait(1000);
+    CPPUNIT_ASSERT_EQUAL(contactBanned, true);
+
+    auto contactInfo = accInfo_.contactModel->getContact(uri);
+    CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, true);
+
+    // Re-ban contact, make sure it isn't a problem
+    accInfo_.contactModel->removeContact(uri, true);
+    contactBanned = WaitForSignalHelper(ConfigurationManager::instance(),
+        SIGNAL(lrc::api::ConversationModel::filterChanged())).wait(1000);
+    CPPUNIT_ASSERT_EQUAL(contactBanned, true);
+
+    contactInfo = accInfo_.contactModel->getContact(uri);
+    CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, true);
+
+    // Unban contact, make sure it worked
+    contactInfo = accInfo_.contactModel->getContact(uri);
+    accInfo_.contactModel->addContact(contactInfo);
+    bool contactUnbanned = WaitForSignalHelper(ConfigurationManager::instance(),
+        SIGNAL(lrc::api::ConversationModel::filterChanged())).wait(1000);
+    CPPUNIT_ASSERT_EQUAL(contactUnbanned, true);
 
+    contactInfo = accInfo_.contactModel->getContact(uri);
+    CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, false);
 }
 
 void
diff --git a/test/contactmodeltester.h b/test/contactmodeltester.h
index 4ea98736..f4d6c1de 100644
--- a/test/contactmodeltester.h
+++ b/test/contactmodeltester.h
@@ -52,6 +52,7 @@ class ContactModelTester :  public CppUnit::TestFixture {
     CPPUNIT_TEST(testRmSIPContact);
     CPPUNIT_TEST(testRmTemporaryContact);
     CPPUNIT_TEST(testCountPendingRequests);
+    CPPUNIT_TEST(testBanUnbanContact);
     CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -116,6 +117,10 @@ public:
      * Count contact requests when banned contacts exists
      */
     void testCountPendingRequestsWithBlockedContact();
+    /**
+     * Try to ban and unban contacts
+     */
+    void testBanUnbanContact();
     /**
      * Method automatically called after each test by CppUnit
      */
diff --git a/test/conversationmodeltester.cpp b/test/conversationmodeltester.cpp
index a66240ef..336b8fcc 100644
--- a/test/conversationmodeltester.cpp
+++ b/test/conversationmodeltester.cpp
@@ -70,35 +70,55 @@ ConversationModelTester::testAddValidConversation()
 void
 ConversationModelTester::testPlaceCallWithBannedContact()
 {
-    // bannedContact should not be in contacts
+    // badguy0 should not be in contacts
     CPPUNIT_ASSERT(!isAContact("badguy0"));
 
+    // so, add him to contacts
     auto uri = addToContacts("badguy0");
-
-    // badguy0 should now be in contacts
     CPPUNIT_ASSERT(isAContact("badguy0"));
 
-    // Ban badguy0
+    // and ban him
     banContact(uri);
     auto contactInfo = accInfo_.contactModel->getContact(uri);
     CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, true);
 
-    // So, now that badguy0 is banned, calling him should be forbidden
+    // find conversation
     auto conversations = accInfo_.conversationModel->allFilteredConversations();
-    bool conversationExists = false;
-    for (const auto& conversation: conversations) {
-        if (std::find(conversation.participants.begin(), conversation.participants.end(), contactInfo.profileInfo.uri) != conversation.participants.end()) {
-            conversationExists = true;
-            // Try to call banned contact
-            auto baseInteractionsSize = conversation.interactions.size();
-            accInfo_.conversationModel->placeCall(conversation.uid);
-            // Make sure call didn't succeed
-            CPPUNIT_ASSERT_EQUAL((int)baseInteractionsSize, (int)conversation.interactions.size());
-            break;
-        }
-    }
+    auto conversation = std::find_if(conversations.begin(), conversations.end(),
+    [&contactInfo](const lrc::api::conversation::Info& conversation) {
+        return std::find(conversation.participants.begin(),
+                         conversation.participants.end(),
+                         contactInfo.profileInfo.uri) != conversation.participants.end();
+    });
 
-    CPPUNIT_ASSERT(conversationExists);
+    CPPUNIT_ASSERT(conversation != conversations.end());
+
+    // now that badguy0 is banned, calling him should be forbidden
+    auto baseInteractionsSize = conversation->interactions.size();
+    accInfo_.conversationModel->placeCall(conversation->uid);
+
+    // make sure call didn't succeed
+    CPPUNIT_ASSERT(conversation->callId.empty());
+
+    // unban badguy0
+    unbanContact(uri);
+    contactInfo = accInfo_.contactModel->getContact(uri);
+    CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, false);
+
+    // call again, should succeed
+    accInfo_.conversationModel->placeCall(conversation->uid);
+
+    // make sure call succeeded
+    conversations = accInfo_.conversationModel->allFilteredConversations();
+    conversation = std::find_if(conversations.begin(), conversations.end(),
+    [&contactInfo](const lrc::api::conversation::Info& conversation) {
+        return std::find(conversation.participants.begin(),
+                         conversation.participants.end(),
+                         contactInfo.profileInfo.uri) != conversation.participants.end();
+    });
+
+    CPPUNIT_ASSERT(conversation != conversations.end());
+    CPPUNIT_ASSERT(!conversation->callId.empty());
 }
 
 void
@@ -109,14 +129,14 @@ ConversationModelTester::testFilterBannedContact()
     CPPUNIT_ASSERT(!isAContact("bannedContacte"));
     CPPUNIT_ASSERT(!isAContact("bannedContac"));
 
-    auto newContactUri = addToContacts("bannedContact");
+    auto uri = addToContacts("bannedContact");
 
     // bannedContact now should be in contacts
     CPPUNIT_ASSERT(isAContact("bannedContact"));
 
-    // Ban bannedContact
-    banContact(newContactUri);
-    auto contactInfo = accInfo_.contactModel->getContact(newContactUri);
+    // ban bannedContact
+    banContact(uri);
+    auto contactInfo = accInfo_.contactModel->getContact(uri);
     CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, true);
 
     // Make sure bannedContact doesn't appear is non-perfect-match filter searches
@@ -142,12 +162,39 @@ ConversationModelTester::testFilterBannedContact()
     CPPUNIT_ASSERT_EQUAL(1, (int)accInfo_.conversationModel->allFilteredConversations().size());
     isTemporary = accInfo_.conversationModel->filteredConversation(0).participants.front() == "";
     CPPUNIT_ASSERT(!isTemporary);
+
+    // Unban bannedContact
+    unbanContact(uri);
+    contactInfo = accInfo_.contactModel->getContact(uri);
+    CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, false);
+
+    // Make sure bannedContact appears is non-perfect-match filter searches
+    // We expect 2 (temporary item + bannedContact)
+    accInfo_.conversationModel->setFilter("bannedContac");
+    WaitForSignalHelper(*accInfo_.contactModel,
+        SIGNAL(modelUpdated())).wait(1000);
+    CPPUNIT_ASSERT_EQUAL(2, (int)accInfo_.conversationModel->allFilteredConversations().size());
+
+    // Here we expect 1 (temporary item)
+    accInfo_.conversationModel->setFilter("bannedContacte");
+    WaitForSignalHelper(*accInfo_.contactModel,
+        SIGNAL(modelUpdated())).wait(1000);
+    CPPUNIT_ASSERT_EQUAL(1, (int)accInfo_.conversationModel->allFilteredConversations().size());
+
+    // Make sure bannedContact appears in perfect-match filter searches
+    // We expect 1 (bannedContact)
+    accInfo_.conversationModel->setFilter("bannedContact");
+    WaitForSignalHelper(*accInfo_.contactModel,
+        SIGNAL(modelUpdated())).wait(1000);
+    CPPUNIT_ASSERT_EQUAL(1, (int)accInfo_.conversationModel->allFilteredConversations().size());
+    isTemporary = accInfo_.conversationModel->filteredConversation(0).participants.front() == "";
+    CPPUNIT_ASSERT(!isTemporary);
 }
 
 void
 ConversationModelTester::testSendMessageToBannedContact()
 {
-    // bannedContact should not be in contacts
+    // badguy1 should not be in contacts
     CPPUNIT_ASSERT(!isAContact("badguy1"));
 
     auto uri = addToContacts("badguy1");
@@ -162,20 +209,41 @@ ConversationModelTester::testSendMessageToBannedContact()
 
     // So, now that badguy is banned, sending a message should be forbidden
     auto conversations = accInfo_.conversationModel->allFilteredConversations();
-    bool conversationExists = false;
-    for (const auto& conversation: conversations) {
-        if (std::find(conversation.participants.begin(), conversation.participants.end(), contactInfo.profileInfo.uri) != conversation.participants.end()) {
-            conversationExists = true;
-            // Try to send message to banned contact
-            auto baseInteractionsSize = conversation.interactions.size();
-            accInfo_.conversationModel->sendMessage(conversation.uid, "Hello banned !");
-            // Make sure message didn't arrive (but contact added is already here)
-            CPPUNIT_ASSERT_EQUAL((int)baseInteractionsSize, (int)conversation.interactions.size());
-            break;
-        }
-    }
+    auto conversation = std::find_if(conversations.begin(), conversations.end(),
+    [&contactInfo](const lrc::api::conversation::Info& conversation) {
+        return std::find(conversation.participants.begin(),
+                         conversation.participants.end(),
+                         contactInfo.profileInfo.uri) != conversation.participants.end();
+    });
 
-    CPPUNIT_ASSERT(conversationExists);
+    CPPUNIT_ASSERT(conversation != conversations.end());
+
+    // Try to send message to banned contact
+    auto baseInteractionsSize = conversation->interactions.size();
+    accInfo_.conversationModel->sendMessage(conversation->uid, "Hello banned !");
+    // Make sure message didn't arrive (but contact added is already here)
+    CPPUNIT_ASSERT_EQUAL((int)baseInteractionsSize, (int)conversation->interactions.size());
+
+    // Unban badguy1
+    unbanContact(uri);
+    contactInfo = accInfo_.contactModel->getContact(uri);
+    CPPUNIT_ASSERT_EQUAL(contactInfo.isBanned, false);
+
+    // Now send message again, should succeed
+    accInfo_.conversationModel->sendMessage(conversation->uid, "Hello unbanned !");
+
+    // Make sure message arrived
+    conversations = accInfo_.conversationModel->allFilteredConversations();
+    conversation = std::find_if(conversations.begin(), conversations.end(),
+    [&contactInfo](const lrc::api::conversation::Info& conversation) {
+        return std::find(conversation.participants.begin(),
+                         conversation.participants.end(),
+                         contactInfo.profileInfo.uri) != conversation.participants.end();
+    });
+
+    CPPUNIT_ASSERT(conversation != conversations.end());
+
+    CPPUNIT_ASSERT_EQUAL((int)baseInteractionsSize + 1, (int)conversation->interactions.size());
 }
 
 void
@@ -375,11 +443,16 @@ ConversationModelTester::testReceiveMessageAndSetRead()
 void
 ConversationModelTester::testPlaceCall()
 {
+    // Get first conversation and make sure it is empty
     auto conversations = accInfo_.conversationModel->allFilteredConversations();
     CPPUNIT_ASSERT(conversations.size() != 0);
     auto firstConversation = accInfo_.conversationModel->filteredConversation(0);
     CPPUNIT_ASSERT(firstConversation.callId.empty());
+
+    // Place a call
     accInfo_.conversationModel->placeCall(firstConversation.uid);
+
+    // Get first conversation again and make sure it isn't empty anymore (call suceeded)
     conversations = accInfo_.conversationModel->allFilteredConversations();
     CPPUNIT_ASSERT(conversations.size() != 0);
     auto newConv = accInfo_.conversationModel->filteredConversation(0);
@@ -487,7 +560,7 @@ ConversationModelTester::tearDown()
 }
 
 bool
-ConversationModelTester::hasConversationWithContact(std::string uri)
+ConversationModelTester::hasConversationWithContact(const std::string& uri)
 {
     auto conversations = accInfo_.conversationModel->allFilteredConversations();
     auto i = std::find_if(conversations.begin(), conversations.end(),
@@ -500,7 +573,7 @@ ConversationModelTester::hasConversationWithContact(std::string uri)
 }
 
 void
-ConversationModelTester::banContact(std::string uri)
+ConversationModelTester::banContact(const std::string& uri)
 {
     accInfo_.contactModel->removeContact(uri, true);
     auto contactBanned = WaitForSignalHelper(ConfigurationManager::instance(),
@@ -508,14 +581,24 @@ ConversationModelTester::banContact(std::string uri)
     CPPUNIT_ASSERT_EQUAL(contactBanned, true);
 }
 
+void
+ConversationModelTester::unbanContact(const std::string& uri)
+{
+    auto contactInfo = accInfo_.contactModel->getContact(uri);
+    accInfo_.contactModel->addContact(contactInfo);
+    auto contactUnbanned = WaitForSignalHelper(ConfigurationManager::instance(),
+        SIGNAL(lrc::api::ConversationModel::filterChanged())).wait(2000);
+    CPPUNIT_ASSERT_EQUAL(contactUnbanned, true);
+}
+
 bool
-ConversationModelTester::isAContact(std::string uri)
+ConversationModelTester::isAContact(const std::string& uri)
 {
     return !accInfo_.contactModel->getContactProfileId(uri).empty();
 }
 
 std::string
-ConversationModelTester::addToContacts(std::string username)
+ConversationModelTester::addToContacts(const std::string& username)
 {
     // Search contact
     accInfo_.conversationModel->setFilter(username);
diff --git a/test/conversationmodeltester.h b/test/conversationmodeltester.h
index 07ff662e..2aac71ff 100644
--- a/test/conversationmodeltester.h
+++ b/test/conversationmodeltester.h
@@ -137,19 +137,23 @@ protected:
     /**
      * Ban contact with passed uri
      */
-    void banContact(std::string uri);
+    void banContact(const std::string& uri);
+    /**
+     * Unban contact with passed uri
+     */
+    void unbanContact(const std::string& uri);
     /**
      * Return whether passed uri already maps to a contact or not
      */
-    bool isAContact(std::string uri);
+    bool isAContact(const std::string& uri);
     /**
      * Add passed usename to contacts and return its uri
      */
-    std::string addToContacts(std::string username);
+    std::string addToContacts(const std::string& username);
     /**
      * Return whether a converation with passed contact uri exists or not
      */
-    bool hasConversationWithContact(std::string uri);
+    bool hasConversationWithContact(const std::string& uri);
 };
 
 } // namespace test
diff --git a/test/mocks/configurationmanager_mock.h b/test/mocks/configurationmanager_mock.h
index ea3eee0d..f23d418b 100644
--- a/test/mocks/configurationmanager_mock.h
+++ b/test/mocks/configurationmanager_mock.h
@@ -52,6 +52,7 @@ class ConfigurationManagerInterface: public QObject
 private:
     QMap<QString, VectorMapStringString> accountToContactsMap;
     QStringList availableContacts_;
+    std::mutex contactsMtx_;
 
 public:
 
@@ -623,30 +624,58 @@ public Q_SLOTS: // METHODS
     {
         if (getAccountList().indexOf(accountId) == -1) return;
         auto contacts = accountToContactsMap[accountId];
-        for (auto c = 0 ; c < contacts.size() ; ++c) {
-            if (contacts.at(c)["id"] == uri) {
-                if (ban) {
-                    contacts[c].insert("removed", "true");
-                    contacts[c].insert("banned", "true");
-                } else {
-                    contacts.remove(c);
-                }
-                emit contactRemoved(accountId, uri, ban);
+
+        {
+            std::lock_guard<std::mutex> lk(contactsMtx_);
+            auto i = std::find_if(
+                contacts.begin(), contacts.end(),
+                [&uri](auto contact) {
+                    return contact["id"] == uri;
+                });
+
+            if (i == contacts.end()) {
                 return;
             }
+
+            if (ban) {
+                i->insert("removed", "true");
+                i->insert("banned", "true");
+            } else {
+                contacts.erase(i);
+            }
         }
+
+        emit contactRemoved(accountId, uri, ban);
     }
 
     void addContact(const QString &accountId, const QString &uri)
     {
         if (getAccountList().indexOf(accountId) == -1) return;
-        auto contact = QMap<QString, QString>();
-        contact.insert("id", uri);
-        contact.insert("added", "true");
-        contact.insert("removed", "false");
-        contact.insert("confirmed", "true");
-        contact.insert("banned", "false");
-        accountToContactsMap[accountId].push_back(contact);
+        auto& cm = accountToContactsMap[accountId];
+
+        {
+            std::lock_guard<std::mutex> lk(contactsMtx_);
+            auto i = std::find_if(
+                cm.begin(), cm.end(),
+                [&uri](auto contact) {
+                    return contact["id"] == uri;
+                });
+
+            if (i != cm.end()) {
+                // Contact is already there, erase it before adding it back.
+                // This is important to reset the banned/removed flags.
+                cm.erase(i);
+            }
+
+            auto contact = QMap<QString, QString>();
+            contact.insert("id", uri);
+            contact.insert("added", "true");
+            contact.insert("removed", "false");
+            contact.insert("confirmed", "true");
+            contact.insert("banned", "false");
+            cm.push_back(contact);
+        }
+
         emit contactAdded(accountId, uri, true);
     }
 
-- 
GitLab