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;