Refactor SDK Core to keep all users from all backends in memory

All users for all backend are kept in memory.
Added unit tests.

Closes: MAASMOB-63
diff --git a/src/mpin_sdk.cpp b/src/mpin_sdk.cpp
index 65608aa..41d34b3 100644
--- a/src/mpin_sdk.cpp
+++ b/src/mpin_sdk.cpp
@@ -121,11 +121,21 @@
 {
 }
 
+String User::GetKey() const
+{
+    return String().Format("%s@%s", m_id.c_str(), m_backend.c_str());
+}
+
 const String& User::GetId() const
 {
     return m_id;
 }
 
+const String& User::GetBackend() const
+{
+    return m_backend;
+}
+
 const String& User::GetDeviceName() const
 {
     return m_deviceName;
@@ -161,6 +171,11 @@
     m_timePermitCache.Set(timePermit, date);
 }
 
+void User::SetBackend(const String& backend)
+{
+    m_backend = backend;
+}
+
 void User::SetStartedRegistration(const String& mpinIdHex, const String& regOTT)
 {
     m_state = STARTED_REGISTRATION;
@@ -193,8 +208,9 @@
     m_state = BLOCKED;
 }
 
-Status User::RestoreState(const String& stateString, const String& mpinIdHex, const String& regOTT)
+Status User::RestoreState(const String& stateString, const String& mpinIdHex, const String& regOTT, const String& backend)
 {
+    SetBackend(backend);
     SetStartedRegistration(mpinIdHex, regOTT);
 
     State state = StringToState(stateString);
@@ -637,6 +653,12 @@
         return Status(Status::FLOW_ERROR, String("CRYPTO_TEE crypto type is currently not supported"));
     }
 
+	Status s = LoadUsersFromStorage();
+    if(s != Status::OK)
+    {
+        return s;
+    }
+
     m_state = INITIALIZED;
     
     String backend = config.Get(CONFIG_BACKEND);
@@ -738,12 +760,6 @@
         return s;
     }
 
-	s = LoadUsersFromStorage();
-    if(s != Status::OK)
-    {
-        return s;
-    }
-
     m_state = BACKEND_SET;
     return Status(Status::OK);
 }
@@ -828,7 +844,8 @@
     bool userIsNew = (user->GetState() == User::INVALID);
     if(userIsNew)
     {
-        AddUser(user);
+        user->SetBackend(MakeBackendKey(m_RPAServer));
+	    m_users[user->GetKey()] = user;
     }
 
     String mpinIdHex = response.GetJsonData().GetStringParam("mpinId");
@@ -837,7 +854,7 @@
 
     if(userIsNew || userDataChanged)
     {
-    	user->SetStartedRegistration(mpinIdHex, regOTT);
+        user->SetStartedRegistration(mpinIdHex, regOTT);
         writeUsersToStorage = true;
     }
 
@@ -1374,92 +1391,9 @@
     return Status::OK;
 }
 
-void MPinSDK::DeleteUser(UserPtr user)
-{
-    DeleteUser(user, m_RPAServer);
-}
-
-void MPinSDK::DeleteUser(INOUT UserPtr user, const String& backend)
-{
-    if(MakeBackendKey(backend) == MakeBackendKey(m_RPAServer))
-    {
-        DeleteUser(user, m_RPAServer, m_users);
-    }
-    else
-    {
-        UsersMap usersMap;
-        LoadUsersFromStorage(backend, usersMap);
-        DeleteUser(user, backend, usersMap);
-    }
-}
-
-void MPinSDK::DeleteUser(INOUT UserPtr user, const String& backend, UsersMap& usersMap)
-{
-    UsersMap::iterator i = usersMap.find(user->GetId());
-    //if(i == m_users.end() || user != i->second)
-    // TODO: Get back to the full user check after the SDK is refactored to store full user list (for all backends)
-    if(i == m_users.end())
-    {
-        return;
-    }
-
-	m_crypto->DeleteRegOTT(i->second->GetMPinId());
-    m_crypto->DeleteToken(i->second->GetMPinId());
-    i->second->Invalidate();
-    m_logoutData.erase(i->second);
-    usersMap.erase(i);
-    WriteUsersToStorage(backend, usersMap);
-}
-
-Status MPinSDK::ListUsers(std::vector<UserPtr>& users) const
-{
-    Status s = CheckIfBackendIsSet();
-    if(s != Status::OK)
-    {
-        return s;
-    }
-
-    ListUsers(users, m_users);
-    return Status::OK;
-}
-
-Status MPinSDK::ListUsers(OUT std::vector<UserPtr>& users, const String& backend) const
-{
-    Status s = CheckIfIsInitialized();
-    if(s != Status::OK)
-    {
-        return s;
-    }
-
-    UsersMap usersMap;
-    s = LoadUsersFromStorage(backend, usersMap);
-    if(s != Status::OK)
-    {
-        return s;
-    }
-
-    ListUsers(users, usersMap);
-    return Status::OK;
-}
-
-void MPinSDK::ListUsers(OUT std::vector<UserPtr>& users, const UsersMap& usersMap) const
-{
-    users.clear();
-    users.reserve(usersMap.size());
-    for(UsersMap::const_iterator i = usersMap.begin(); i != usersMap.end(); ++i)
-    {
-        users.push_back(i->second);
-    }
-}
-
-void MPinSDK::AddUser(UserPtr user)
-{
-	m_users[user->GetId()] = user;
-}
-
 Status MPinSDK::CheckUserState(UserPtr user, User::State expectedState)
 {
-    UsersMap::iterator i = m_users.find(user->GetId());
+    UsersMap::iterator i = m_users.find(user->GetKey());
     if(expectedState == User::INVALID)
     {
         if(i != m_users.end())
@@ -1483,7 +1417,12 @@
 
     if(user != i->second)
     {
-        return Status(Status::FLOW_ERROR, String().Format("Different user with the '%s' id was previously added", user->GetId().c_str()));
+        return Status(Status::FLOW_ERROR, String().Format("Different user object with the '%s' id was previously added", user->GetId().c_str()));
+    }
+
+    if(user->GetBackend() != MakeBackendKey(m_RPAServer))
+    {
+        return Status(Status::FLOW_ERROR, String().Format("User '%s' is registered within a different backend than the current one", user->GetId().c_str()));
     }
 
     if(user->GetState() != expectedState)
@@ -1495,172 +1434,66 @@
     return Status(Status::OK);
 }
 
-String MPinSDK::MakeBackendKey(const String& backendServer) const
+void MPinSDK::DeleteUser(UserPtr user)
 {
-    String backend = backendServer;
-    backend.ReplaceAll("https://", "");
-    backend.ReplaceAll("http://", "");
-    backend.TrimRight("/");
-    return backend;
+    Status s = CheckIfIsInitialized();
+    if(s != Status::OK)
+    {
+        return;
+    }
+
+    UsersMap::iterator i = m_users.find(user->GetKey());
+    if(i == m_users.end() || user != i->second)
+    {
+        return;
+    }
+
+	m_crypto->DeleteRegOTT(i->second->GetMPinId());
+    m_crypto->DeleteToken(i->second->GetMPinId());
+    i->second->Invalidate();
+    m_logoutData.erase(i->second);
+    m_users.erase(i);
+    WriteUsersToStorage();
 }
 
-Status MPinSDK::WriteUsersToStorage() const
+Status MPinSDK::ListUsers(OUT std::vector<UserPtr>& users, const String& backend) const
 {
-    return WriteUsersToStorage(m_RPAServer, m_users);
-}
+    Status s = CheckIfIsInitialized();
+    if(s != Status::OK)
+    {
+        return s;
+    }
 
-Status MPinSDK::WriteUsersToStorage(const String& backendServer, const UsersMap& usersMap) const
-{
-	IStorage* storage = m_context->GetStorage(IStorage::NONSECURE);
-	String data;
-	storage->GetData(data);
-    data.Trim();
-	
-	try
-	{
-        json::Object allBackendsObject;
-        if(!data.empty())
+    users.clear();
+    users.reserve(m_users.size());
+
+    String backendKey = MakeBackendKey(backend);
+
+    for(UsersMap::const_iterator i = m_users.begin(); i != m_users.end(); ++i)
+    {
+        if(backendKey.empty() || backendKey == i->second->GetBackend())
         {
-            std::istringstream strIn(data);
-            json::Reader::Read(allBackendsObject, strIn);
+            users.push_back(i->second);
         }
-
-        String backend = MakeBackendKey(backendServer);
-
-        json::Object& rootObject = (json::Object&) allBackendsObject[backend];
-        rootObject.Clear();
-		
-		for (UsersMap::const_iterator i = usersMap.begin(); i != usersMap.end(); ++i)
-		{
-			UserPtr user = i->second;
-
-            json::Object timePermitCacheObject;
-            timePermitCacheObject["date"] = json::Number(user->GetTimePermitCache().GetDate());
-            timePermitCacheObject["timePermit"] = json::String(util::HexEncode(user->GetTimePermitCache().GetTimePermit()));
-
-			json::Object userObject;
-            userObject["timePermitCache"] = timePermitCacheObject;
-
-            if(!user->GetDeviceName().empty())
-            {
-                userObject["deviceName"] = json::String(user->GetDeviceName());
-            }
-
-            userObject["state"] = json::String(user->GetStateString());
-
-            rootObject[user->GetMPinIdHex()] = userObject;
-			
-            Status s;
-            switch(user->GetState())
-            {
-            case User::STARTED_REGISTRATION:
-            case User::ACTIVATED:
-                s = m_crypto->SaveRegOTT(user->GetMPinId(), user->GetRegOTT());
-                break;
-            case User::REGISTERED:
-                s = m_crypto->DeleteRegOTT(user->GetMPinId());
-                break;
-            default:
-                break;
-            }
-			if(s != Status::OK)
-			{
-				return s;
-			}
-		}
-
-		std::stringstream strOut;
-		json::Writer::Write(allBackendsObject, strOut);
-		storage->SetData(strOut.str());
-		
-	}
-    catch(const json::Exception& e)
-    {
-        return Status(Status::STORAGE_ERROR, e.what());
     }
-    
-    return Status(Status::OK);
+
+    return Status::OK;
 }
 
-Status MPinSDK::LoadUsersFromStorage()
+Status MPinSDK::ListUsers(OUT std::vector<UserPtr>& users) const
 {
-    ClearUsers();
-    return LoadUsersFromStorage(m_RPAServer, m_users);
+    Status s = CheckIfBackendIsSet();
+    if(s != Status::OK)
+    {
+        return s;
+    }
+
+    return ListUsers(users, m_RPAServer);
 }
 
-Status MPinSDK::LoadUsersFromStorage(const String& backendServer, UsersMap& usersMap) const
+Status MPinSDK::ListAllUsers(OUT std::vector<UserPtr>& users) const
 {
-	IStorage* storage = m_context->GetStorage(IStorage::NONSECURE);
-	String data;
-	storage->GetData(data);
-    data.Trim();
-	if(data.empty())
-    {
-		return Status(Status::OK);
-	}
-
-	try
-    {
-        json::Object allBackendsObject;
-        std::istringstream str(data);
-        json::Reader::Read(allBackendsObject, str);
-
-        String backend = MakeBackendKey(backendServer);
-
-        json::Object::const_iterator i = allBackendsObject.Find(backend);
-        if(i == allBackendsObject.End())
-        {
-            return Status(Status::OK);
-        }
-
-        const json::Object& rootObject = (const json::Object&) i->element;
-        for(i = rootObject.Begin(); i != rootObject.End(); ++i)
-        {
-            const String& mpinIdHex = i->name;
-			String mpinId = util::HexDecode(mpinIdHex);
-			util::JsonObject mpinIdJson;
-			if(!mpinIdJson.Parse(mpinId.c_str()))
-            {
-                return Status(Status::STORAGE_ERROR, String().Format("Failed to parse mpinId json: '%s'", mpinId.c_str()));
-            }
-            const json::Object& userObject = (const json::Object&) i->element;
-			const std::string& id = ((const json::String&) mpinIdJson["userID"]).Value();
-            std::string deviceName;
-            json::Object::const_iterator dni = userObject.Find("deviceName");
-            if(dni != userObject.End())
-            {
-                deviceName = ((const json::String&) dni->element).Value();
-            }
-
-			String regOTT;
-            Status s = m_crypto->LoadRegOTT(mpinId, regOTT);
-			if(s != Status::OK)
-			{
-				return s;
-			}
-
-            UserPtr user = MakeNewUser(id, deviceName);
-            s = user->RestoreState(((const json::String&) userObject["state"]).Value(), mpinIdHex, regOTT);
-            if(s != Status::OK)
-            {
-                return s;
-            }
-
-            const json::Object& timePermitCacheObject = (const json::Object&) userObject["timePermitCache"];
-            int date = (int) ((const json::Number&) timePermitCacheObject["date"]).Value();
-            const String& timePermit = util::HexDecode(((const json::String&) timePermitCacheObject["timePermit"]).Value());
-
-            user->CacheTimePermit(timePermit, date);
-
-            usersMap[id] = user;
-		}
-    }
-    catch(const json::Exception& e)
-    {
-        return Status(Status::STORAGE_ERROR, e.what());
-    }
-
-	return Status(Status::OK);
+    return ListUsers(users, "");
 }
 
 Status MPinSDK::ListBackends(OUT std::vector<String>& backends) const
@@ -1671,9 +1504,10 @@
         return s;
     }
 
-    IStorage* storage = m_context->GetStorage(IStorage::NONSECURE);
-	String data;
-	storage->GetData(data);
+    backends.clear();
+
+    String data;
+    m_context->GetStorage(IStorage::NONSECURE)->GetData(data);
     data.Trim();
 	if(data.empty())
     {
@@ -1699,9 +1533,141 @@
     return Status::OK;
 }
 
-const char * MPinSDK::GetVersion()
+String MPinSDK::MakeBackendKey(const String& backendServer) const
 {
-    return MPIN_SDK_V2_VERSION;
+    String backend = backendServer;
+    backend.ReplaceAll("https://", "");
+    backend.ReplaceAll("http://", "");
+    backend.TrimRight("/");
+    return backend;
+}
+
+Status MPinSDK::WriteUsersToStorage() const
+{
+	try
+	{
+        json::Object rootObject;
+        for (UsersMap::const_iterator i = m_users.begin(); i != m_users.end(); ++i)
+		{
+			UserPtr user = i->second;
+
+            json::Object timePermitCacheObject;
+            timePermitCacheObject["date"] = json::Number(user->GetTimePermitCache().GetDate());
+            timePermitCacheObject["timePermit"] = json::String(util::HexEncode(user->GetTimePermitCache().GetTimePermit()));
+
+			json::Object userObject;
+            userObject["timePermitCache"] = timePermitCacheObject;
+
+            if(!user->GetDeviceName().empty())
+            {
+                userObject["deviceName"] = json::String(user->GetDeviceName());
+            }
+
+            userObject["state"] = json::String(user->GetStateString());
+
+            ((json::Object&) rootObject[user->GetBackend()])[user->GetMPinIdHex()] = userObject;
+
+            Status s;
+            switch(user->GetState())
+            {
+            case User::STARTED_REGISTRATION:
+            case User::ACTIVATED:
+                s = m_crypto->SaveRegOTT(user->GetMPinId(), user->GetRegOTT());
+                break;
+            case User::REGISTERED:
+                s = m_crypto->DeleteRegOTT(user->GetMPinId());
+                break;
+            default:
+                break;
+            }
+			if(s != Status::OK)
+			{
+				return s;
+			}
+        }
+
+		std::stringstream strOut;
+		json::Writer::Write(rootObject, strOut);
+		m_context->GetStorage(IStorage::NONSECURE)->SetData(strOut.str());
+	}
+    catch(const json::Exception& e)
+    {
+        return Status(Status::STORAGE_ERROR, e.what());
+    }
+
+    return Status::OK;
+}
+
+Status MPinSDK::LoadUsersFromStorage()
+{
+    ClearUsers();
+
+	String data;
+	m_context->GetStorage(IStorage::NONSECURE)->GetData(data);
+    data.Trim();
+	if(data.empty())
+    {
+		return Status(Status::OK);
+	}
+
+	try
+    {
+        json::Object rootObject;
+        std::istringstream str(data);
+        json::Reader::Read(rootObject, str);
+
+        for(json::Object::const_iterator backendsIter = rootObject.Begin(); backendsIter != rootObject.End(); ++backendsIter)
+        {
+            const json::Object& backendObject = (const json::Object&) backendsIter->element;
+
+            for(json::Object::const_iterator usersIter = backendObject.Begin(); usersIter != backendObject.End(); ++usersIter)
+            {
+                const String& mpinIdHex = usersIter->name;
+			    String mpinId = util::HexDecode(mpinIdHex);
+			    util::JsonObject mpinIdJson;
+			    if(!mpinIdJson.Parse(mpinId.c_str()))
+                {
+                    return Status(Status::STORAGE_ERROR, String().Format("Failed to parse mpinId json: '%s'", mpinId.c_str()));
+                }
+                const json::Object& userObject = (const json::Object&) usersIter->element;
+			    const std::string& id = ((const json::String&) mpinIdJson["userID"]).Value();
+                std::string deviceName;
+                json::Object::const_iterator dni = userObject.Find("deviceName");
+                if(dni != userObject.End())
+                {
+                    deviceName = ((const json::String&) dni->element).Value();
+                }
+
+			    String regOTT;
+                Status s = m_crypto->LoadRegOTT(mpinId, regOTT);
+			    if(s != Status::OK)
+			    {
+				    return s;
+			    }
+
+                UserPtr user = MakeNewUser(id, deviceName);
+                s = user->RestoreState(((const json::String&) userObject["state"]).Value(), mpinIdHex, regOTT, backendsIter->name);
+                if(s != Status::OK)
+                {
+                    return s;
+                }
+
+                const json::Object& timePermitCacheObject = (const json::Object&) userObject["timePermitCache"];
+                int date = (int) ((const json::Number&) timePermitCacheObject["date"]).Value();
+                const String& timePermit = util::HexDecode(((const json::String&) timePermitCacheObject["timePermit"]).Value());
+
+                user->CacheTimePermit(timePermit, date);
+
+                m_users[user->GetKey()] = user;
+            }
+        }
+    }
+    catch(const json::Exception& e)
+    {
+        return Status(Status::STORAGE_ERROR, e.what());
+    }
+
+    return Status::OK;
 }
 
 bool MPinSDK::CanLogout(UserPtr user)
@@ -1773,3 +1739,8 @@
 	m_clientSettings[key].Accept(sv);
 	return sv.GetData();
 }
+
+const char * MPinSDK::GetVersion()
+{
+    return MPIN_SDK_V2_VERSION;
+}
diff --git a/src/mpin_sdk.h b/src/mpin_sdk.h
index bd7a2f1..77ca6f6 100644
--- a/src/mpin_sdk.h
+++ b/src/mpin_sdk.h
@@ -173,29 +173,33 @@
         };
 
         const String& GetId() const;
+        const String& GetBackend() const;
         const String& GetMPinId() const;
         State GetState() const;
 
     private:
         friend class MPinSDK;
         User(const String& id, const String& deviceName);
+        String GetKey() const;
         const String& GetDeviceName() const;
         const String& GetMPinIdHex() const;
         const String& GetRegOTT() const;
         const TimePermitCache& GetTimePermitCache() const;
         void CacheTimePermit(const String& timePermit, int date);
+        void SetBackend(const String& backend);
         void SetStartedRegistration(const String& mpinIdHex, const String& regOTT);
         void SetActivated();
         void SetRegistered();
         void Invalidate();
         void Block();
-        Status RestoreState(const String& stateString, const String& mpinIdHex, const String& regOTT);
+        Status RestoreState(const String& stateString, const String& mpinIdHex, const String& regOTT, const String& backend);
         String GetStateString() const;
         static String StateToString(State state);
         static State StringToState(const String& stateString);
 
     private:
         String m_id;
+        String m_backend;
         String m_deviceName;
         State m_state;
         String m_mpinId;
@@ -256,14 +260,14 @@
     Status GetSessionDetails(const String& accessCode, OUT SessionDetails& sessionDetails);
 
     void DeleteUser(INOUT UserPtr user);
-    void DeleteUser(INOUT UserPtr user, const String& backend);
-    Status ListUsers(OUT std::vector<UserPtr>& users) const;
     Status ListUsers(OUT std::vector<UserPtr>& users, const String& backend) const;
+    Status ListUsers(OUT std::vector<UserPtr>& users) const;
+    Status ListAllUsers(OUT std::vector<UserPtr>& users) const;
     Status ListBackends(OUT std::vector<String>& backends) const;
-    const char * GetVersion();
     bool CanLogout(IN UserPtr user);
     bool Logout(IN UserPtr user);
 	String GetClientParam(const String& key);
+    const char * GetVersion();
 
     static const char *CONFIG_BACKEND;
     static const char *CONFIG_RPS_PREFIX;
@@ -361,15 +365,10 @@
     Status GetCertivoxTimePermitShare(INOUT UserPtr user, const util::JsonObject& cutomerTimePermitData, OUT String& resultTimePermit);
     bool ValidateAccessNumber(const String& accessNumber);
     bool ValidateAccessNumberChecksum(const String& accessNumber);
-    void AddUser(IN UserPtr user);
     Status CheckUserState(IN UserPtr user, User::State expectedState);
-    void DeleteUser(INOUT UserPtr user, const String& backend, UsersMap& usersMap);
     String MakeBackendKey(const String& backendServer) const;
 	Status WriteUsersToStorage() const;
-	Status WriteUsersToStorage(const String& backendServer, const UsersMap& usersMap) const;
 	Status LoadUsersFromStorage();
-    Status LoadUsersFromStorage(const String& backendServer, UsersMap& usersMap) const;
-    void ListUsers(OUT std::vector<UserPtr>& users, const UsersMap& usersMap) const;
 
     static const char *DEFAULT_RPS_PREFIX;
     static const int AN_WITH_CHECKSUM_LEN = 7;
diff --git a/src/version.h b/src/version.h
index a9b268d..1ae5af5 100644
--- a/src/version.h
+++ b/src/version.h
@@ -25,6 +25,6 @@
 #define _MPIN_SDK_VERSION_H_
 
 #define MPIN_SDK_VERSION "1.0.0"
-#define MPIN_SDK_V2_VERSION "2.1.0"
+#define MPIN_SDK_V2_VERSION "2.2.0"
 
 #endif // _MPIN_SDK_VERSION_H_
diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp
index 055d7ee..f963195 100644
--- a/tests/unit_tests.cpp
+++ b/tests/unit_tests.cpp
@@ -191,10 +191,14 @@
     }
 
     users.clear();
+    sdk.ListAllUsers(users);
+    BOOST_CHECK_EQUAL(users.size(), 1);
+
+    users.clear();
     sdk.ListUsers(users, backend);
     BOOST_CHECK_EQUAL(users.size(), 1);
 
-    sdk.DeleteUser(users[0], backend);
+    sdk.DeleteUser(users[0]);
     users.clear();
     sdk.ListUsers(users, backend);
     BOOST_CHECK(users.empty());