Serialization: Adding Serialization for MFA

This commit will add the persistency of Dbus properties related to MFA.
The configuration file will be stored under
/var/lib/usr_mgr.conf.

Change-Id: Ib7fdc467c7cb094d328ae670df3bb4352e4a7b91
Signed-off-by: Abhilash Raju <abhilash.kollam@gmail.com>
diff --git a/json_serializer.hpp b/json_serializer.hpp
new file mode 100644
index 0000000..81a6a4d
--- /dev/null
+++ b/json_serializer.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <format>
+#include <fstream>
+#include <ranges>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+class JsonSerializer
+{
+  public:
+    JsonSerializer(std::string path, nlohmann::json js = nlohmann::json()) :
+        mfaConfPath(path), jsonData(std::move(js))
+    {}
+
+    inline auto stringSplitter()
+    {
+        return std::views::split('/') | std::views::transform([](auto&& sub) {
+                   return std::string(sub.begin(), sub.end());
+               });
+    }
+    nlohmann::json makeJson(const std::string& key, const std::string& value)
+    {
+        auto keys = key | stringSplitter();
+        std::vector v(keys.begin(), keys.end());
+        auto rv = v | std::views::reverse;
+        nlohmann::json init;
+        init[rv.front()] = value;
+        auto newJson = std::reduce(rv.begin() + 1, rv.end(), init,
+                                   [](auto sofar, auto currentKey) {
+                                       nlohmann::json j;
+                                       j[currentKey] = sofar;
+                                       return j;
+                                   });
+        return newJson;
+    }
+    std::optional<nlohmann::json> getLeafNode(const std::string_view keyPath)
+    {
+        auto keys = keyPath | stringSplitter();
+        nlohmann::json current = jsonData;
+        for (auto key : keys)
+        {
+            if (!current.contains(key))
+            {
+                return std::nullopt;
+            }
+            current = current[key];
+        }
+        return current;
+    }
+    void serialize(std::string key, const std::string value)
+    {
+        jsonData.merge_patch(makeJson(key, value));
+    }
+    template <typename T>
+    void deserialize(std::string key, T& value)
+    {
+        auto leaf = getLeafNode(key);
+        if (leaf)
+        {
+            value = *leaf;
+        }
+    }
+    void erase(std::string key)
+    {
+        if (jsonData.contains(key))
+        {
+            jsonData.erase(key);
+        }
+    }
+    bool store()
+    {
+        std::filesystem::path dir =
+            std::filesystem::path(mfaConfPath).parent_path();
+
+        // Check if the directory exists, and create it if it does not
+        if (!dir.string().empty() && !std::filesystem::exists(dir))
+        {
+            std::error_code ec;
+            if (!std::filesystem::create_directories(dir, ec))
+            {
+                lg2::error("Unable to create directory {DIR}", "DIR",
+                           dir.string());
+                return false;
+            }
+        }
+        std::ofstream file(mfaConfPath.data());
+        if (file.is_open())
+        {
+            file << jsonData.dump(4); // Pretty print with 4 spaces
+            file.close();
+            return true;
+        }
+        else
+        {
+            lg2::error("Unable to open file {FILENAME}", "FILENAME",
+                       mfaConfPath);
+            return false;
+        }
+    }
+    void load()
+    {
+        std::ifstream file(mfaConfPath.data());
+
+        if (file.is_open())
+        {
+            file >> jsonData;
+            file.close();
+        }
+        else
+        {
+            lg2::error("Unable to open file for reading {FILENAME}", "FILENAME",
+                       mfaConfPath);
+        }
+    }
+
+  private:
+    const std::string mfaConfPath;
+    nlohmann::json jsonData;
+};
diff --git a/user_mgr.cpp b/user_mgr.cpp
index b7afd16..b8b3afd 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -40,6 +40,7 @@
 #include <algorithm>
 #include <array>
 #include <ctime>
+#include <filesystem>
 #include <fstream>
 #include <numeric>
 #include <regex>
@@ -47,7 +48,6 @@
 #include <string>
 #include <string_view>
 #include <vector>
-
 namespace phosphor
 {
 namespace user
@@ -107,7 +107,7 @@
 
 namespace
 {
-
+constexpr auto mfaConfPath = "/var/lib/usr_mgr.conf";
 // The hardcoded groups in OpenBMC projects
 constexpr std::array<const char*, 4> predefinedGroups = {
     "redfish", "ipmi", "ssh", "hostconsole"};
@@ -382,7 +382,7 @@
     usersList.emplace(
         userName, std::make_unique<phosphor::user::Users>(
                       bus, userObj.c_str(), groupNames, priv, enabled, *this));
-
+    serializer.store();
     lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
     return;
 }
@@ -406,7 +406,7 @@
     }
 
     usersList.erase(userName);
-
+    serializer.store();
     lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
     return;
 }
@@ -1476,19 +1476,37 @@
     }
 }
 
+void UserMgr::load()
+{
+    std::optional<std::string> authTypeStr;
+    if (std::filesystem::exists(mfaConfPath))
+    {
+        serializer.load();
+        serializer.deserialize("authtype", authTypeStr);
+    }
+    auto authType =
+        authTypeStr.transform(MultiFactorAuthConfiguration::convertStringToType)
+            .value_or(std::optional(MultiFactorAuthType::None));
+    if (authType)
+    {
+        enabled(*authType, true);
+    }
+}
+
 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
-    faillockConfigFile(defaultFaillockConfigFile),
+    serializer(mfaConfPath), faillockConfigFile(defaultFaillockConfigFile),
     pwHistoryConfigFile(defaultPWHistoryConfigFile),
     pwQualityConfigFile(defaultPWQualityConfigFile)
+
 {
     UserMgrIface::allPrivileges(privMgr);
     groupsMgr = readAllGroupsOnSystem();
     std::sort(groupsMgr.begin(), groupsMgr.end());
     UserMgrIface::allGroups(groupsMgr);
     initializeAccountPolicy();
+    load();
     initUserObjects();
-
     // emit the signal
     this->emit_object_added();
 }
@@ -1565,6 +1583,9 @@
             }
             break;
     }
+    serializer.serialize(
+        "authtype", MultiFactorAuthConfiguration::convertTypeToString(value));
+    serializer.store();
     return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
 }
 bool UserMgr::secretKeyRequired(std::string userName)
diff --git a/user_mgr.hpp b/user_mgr.hpp
index 624b2ba..b69cb87 100644
--- a/user_mgr.hpp
+++ b/user_mgr.hpp
@@ -14,6 +14,7 @@
 // limitations under the License.
 */
 #pragma once
+#include "json_serializer.hpp"
 #include "users.hpp"
 
 #include <boost/process/child.hpp>
@@ -278,6 +279,11 @@
                                 bool skipSignal) override;
     bool secretKeyRequired(std::string userName) override;
     static std::vector<std::string> readAllGroupsOnSystem();
+    void load();
+    JsonSerializer& getSerializer()
+    {
+        return serializer;
+    }
 
   protected:
     /** @brief get pam argument value
@@ -441,6 +447,8 @@
     /** @brief object path */
     const std::string path;
 
+    /** @brief serializer for mfa */
+    JsonSerializer serializer;
     /** @brief privilege manager container */
     const std::vector<std::string> privMgr = {"priv-admin", "priv-operator",
                                               "priv-user"};
@@ -449,8 +457,8 @@
     std::vector<std::string> groupsMgr;
 
     /** @brief map container to hold users object */
-    using UserName = std::string;
-    std::unordered_map<UserName, std::unique_ptr<phosphor::user::Users>>
+
+    std::unordered_map<std::string, std::unique_ptr<phosphor::user::Users>>
         usersList;
 
     /** @brief get users in group
diff --git a/users.cpp b/users.cpp
index 0300358..9008008 100644
--- a/users.cpp
+++ b/users.cpp
@@ -76,10 +76,13 @@
     UsersIface::userPrivilege(priv, true);
     UsersIface::userGroups(groups, true);
     UsersIface::userEnabled(enabled, true);
-
+    load(manager.getSerializer());
     this->emit_object_added();
 }
-
+Users::~Users()
+{
+    manager.getSerializer().erase(userName);
+}
 /** @brief delete user method.
  *  This method deletes the user as requested
  *
@@ -323,6 +326,10 @@
     {
         iter->second(*this);
     }
+    std::string path = std::format("{}/bypassedprotocol", getUserName());
+    manager.getSerializer().serialize(
+        path, MultiFactorAuthConfiguration::convertTypeToString(value));
+    manager.getSerializer().store();
     return Interfaces::bypassedProtocol(value, skipSignal);
 }
 
@@ -362,5 +369,22 @@
     clearGoogleAuthenticator(*this);
 }
 
+void Users::load(JsonSerializer& ts)
+{
+    std::optional<std::string> protocol;
+    std::string path = std::format("{}/bypassedprotocol", userName);
+    ts.deserialize(path, protocol);
+    if (protocol)
+    {
+        MultiFactorAuthType type =
+            MultiFactorAuthConfiguration::convertTypeFromString(*protocol);
+        bypassedProtocol(type, true);
+        return;
+    }
+    bypassedProtocol(MultiFactorAuthType::None, true);
+    ts.serialize(path, MultiFactorAuthConfiguration::convertTypeToString(
+                           MultiFactorAuthType::None));
+}
+
 } // namespace user
 } // namespace phosphor
diff --git a/users.hpp b/users.hpp
index a5ff131..6d66598 100644
--- a/users.hpp
+++ b/users.hpp
@@ -14,6 +14,8 @@
 // limitations under the License.
 */
 #pragma once
+#include "json_serializer.hpp"
+
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/object.hpp>
 #include <xyz/openbmc_project/Object/Delete/server.hpp>
@@ -34,6 +36,8 @@
                                                TOTPAuthenticatorIface>;
 using MultiFactorAuthType = sdbusplus::common::xyz::openbmc_project::user::
     MultiFactorAuthConfiguration::Type;
+using MultiFactorAuthConfiguration =
+    sdbusplus::common::xyz::openbmc_project::user::MultiFactorAuthConfiguration;
 // Place where all user objects has to be created
 constexpr auto usersObjPath = "/xyz/openbmc_project/user";
 
@@ -46,7 +50,7 @@
 {
   public:
     Users() = delete;
-    ~Users() = default;
+    ~Users();
     Users(const Users&) = delete;
     Users& operator=(const Users&) = delete;
     Users(Users&&) = delete;
@@ -139,6 +143,7 @@
     MultiFactorAuthType bypassedProtocol(MultiFactorAuthType value,
                                          bool skipSignal) override;
     void enableMultiFactorAuth(MultiFactorAuthType type, bool value);
+    void load(JsonSerializer& serializer);
 
   private:
     bool checkMfaStatus() const;