PamModuleArgValue: add unit test coverage
This commit refactorred the codes such that it becomes possible to unit
test the two non-trivial method: setPamModuleArgValue and
getPamModuleArgValue.
The unit test manipulates temporary files in the unit test container
instead of real pam configs.
Check the coverage data before and after this commit to see how these
two files now get nearly 100% unit test coverage.
Tested: unit test.
Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Change-Id: Ibad461ace0eae89183de0ddbfb189ee0458b1d2e
diff --git a/test/user_mgr_test.cpp b/test/user_mgr_test.cpp
index ee45482..f16a9d6 100644
--- a/test/user_mgr_test.cpp
+++ b/test/user_mgr_test.cpp
@@ -6,6 +6,8 @@
#include <xyz/openbmc_project/User/Common/error.hpp>
#include <exception>
+#include <filesystem>
+#include <fstream>
#include <gtest/gtest.h>
@@ -183,5 +185,130 @@
EXPECT_EQ(str, "");
}
+namespace
+{
+inline constexpr const char* objectRootInTest = "/xyz/openbmc_project/user";
+
+// Fake config; referenced config on real BMC
+inline constexpr const char* rawConfig = R"(
+#
+# /etc/pam.d/common-password - password-related modules common to all services
+#
+# This file is included from other service-specific PAM config files,
+# and should contain a list of modules that define the services to be
+# used to change user passwords. The default is pam_unix.
+
+# Explanation of pam_unix options:
+#
+# The "sha512" option enables salted SHA512 passwords. Without this option,
+# the default is Unix crypt. Prior releases used the option "md5".
+#
+# The "obscure" option replaces the old `OBSCURE_CHECKS_ENAB' option in
+# login.defs.
+#
+# See the pam_unix manpage for other options.
+
+# here are the per-package modules (the "Primary" block)
+password [success=ok default=die] pam_tally2.so debug enforce_for_root reject_username minlen=8 difok=0 lcredit=0 ocredit=0 dcredit=0 ucredit=0 #some comments
+password [success=ok default=die] pam_cracklib.so debug enforce_for_root reject_username minlen=8 difok=0 lcredit=0 ocredit=0 dcredit=0 ucredit=0 #some comments
+password [success=ok default=die] pam_ipmicheck.so spec_grp_name=ipmi use_authtok
+password [success=ok ignore=ignore default=die] pam_pwhistory.so debug enforce_for_root remember=0 use_authtok
+password [success=ok default=die] pam_unix.so sha512 use_authtok
+password [success=1 default=die] pam_ipmisave.so spec_grp_name=ipmi spec_pass_file=/etc/ipmi_pass key_file=/etc/key_file
+# here's the fallback if no module succeeds
+password requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+password required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+)";
+} // namespace
+
+void dumpStringToFile(const std::string& str, const std::string& filePath)
+{
+ std::ofstream outputFileStream;
+
+ outputFileStream.exceptions(std::ofstream::failbit | std::ofstream::badbit |
+ std::ofstream::eofbit);
+
+ outputFileStream.open(filePath, std::ios::out);
+ outputFileStream << str << "\n" << std::flush;
+ outputFileStream.close();
+}
+
+void removeFile(const std::string& filePath)
+{
+ std::filesystem::remove(filePath);
+}
+
+class UserMgrInTest : public testing::Test, public UserMgr
+{
+ public:
+ UserMgrInTest() : UserMgr(busInTest, objectRootInTest)
+ {
+ tempPamConfigFile = "/tmp/test-data-XXXXXX";
+ mktemp(tempPamConfigFile.data());
+ EXPECT_NO_THROW(dumpStringToFile(rawConfig, tempPamConfigFile));
+ // Set config files to test files
+ pamPasswdConfigFile = tempPamConfigFile;
+ pamAuthConfigFile = tempPamConfigFile;
+ }
+
+ ~UserMgrInTest() override
+ {
+ EXPECT_NO_THROW(removeFile(tempPamConfigFile));
+ }
+
+ protected:
+ static sdbusplus::bus_t busInTest;
+ std::string tempPamConfigFile;
+};
+
+sdbusplus::bus_t UserMgrInTest::busInTest = sdbusplus::bus::new_default();
+
+TEST_F(UserMgrInTest, GetPamModuleArgValueOnSuccess)
+{
+ std::string minLen;
+ EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
+ EXPECT_EQ(minLen, "8");
+ EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
+ EXPECT_EQ(minLen, "8");
+}
+
+TEST_F(UserMgrInTest, SetPamModuleArgValueOnSuccess)
+{
+ EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), 0);
+ EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), 0);
+ std::string minLen;
+ EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
+ EXPECT_EQ(minLen, "16");
+ EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
+ EXPECT_EQ(minLen, "16");
+}
+
+TEST_F(UserMgrInTest, GetPamModuleArgValueOnFailure)
+{
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
+ std::string minLen;
+ EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
+ EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
+
+ EXPECT_NO_THROW(removeFile(tempPamConfigFile));
+ EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
+ EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
+}
+
+TEST_F(UserMgrInTest, SetPamModuleArgValueOnFailure)
+{
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
+ EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
+ EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
+
+ EXPECT_NO_THROW(removeFile(tempPamConfigFile));
+ EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
+ EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
+}
+
} // namespace user
} // namespace phosphor
diff --git a/user_mgr.cpp b/user_mgr.cpp
index b2047b2..390981c 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -72,8 +72,10 @@
static constexpr const char* remOldPasswdCount = "remember";
static constexpr const char* maxFailedAttempt = "deny";
static constexpr const char* unlockTimeout = "unlock_time";
-static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password";
-static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
+static constexpr const char* defaultPamPasswdConfigFile =
+ "/etc/pam.d/common-password";
+static constexpr const char* defaultPamAuthConfigFile =
+ "/etc/pam.d/common-auth";
// Object Manager related
static constexpr const char* ldapMgrObjBasePath =
@@ -1226,7 +1228,9 @@
}
UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
- Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
+ Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
+ pamPasswdConfigFile(defaultPamPasswdConfigFile),
+ pamAuthConfigFile(defaultPamAuthConfigFile)
{
UserMgrIface::allPrivileges(privMgr);
std::sort(groupsMgr.begin(), groupsMgr.end());
diff --git a/user_mgr.hpp b/user_mgr.hpp
index 4a82950..ac163f7 100644
--- a/user_mgr.hpp
+++ b/user_mgr.hpp
@@ -193,6 +193,33 @@
**/
UserInfoMap getUserInfo(std::string userName) override;
+ protected:
+ /** @brief get pam argument value
+ * method to get argument value from pam configuration
+ *
+ * @param[in] moduleName - name of the module from where arg has to be read
+ * @param[in] argName - argument name
+ * @param[out] argValue - argument value
+ *
+ * @return 0 - success state of the function
+ */
+ int getPamModuleArgValue(const std::string& moduleName,
+ const std::string& argName, std::string& argValue);
+
+ /** @brief set pam argument value
+ * method to set argument value in pam configuration
+ *
+ * @param[in] moduleName - name of the module in which argument value has
+ * to be set
+ * @param[in] argName - argument name
+ * @param[out] argValue - argument value
+ *
+ * @return 0 - success state of the function
+ */
+ int setPamModuleArgValue(const std::string& moduleName,
+ const std::string& argName,
+ const std::string& argValue);
+
private:
/** @brief sdbusplus handler */
sdbusplus::bus_t& bus;
@@ -302,32 +329,6 @@
*/
size_t getIpmiUsersCount(void);
- /** @brief get pam argument value
- * method to get argument value from pam configuration
- *
- * @param[in] moduleName - name of the module from where arg has to be read
- * @param[in] argName - argument name
- * @param[out] argValue - argument value
- *
- * @return 0 - success state of the function
- */
- int getPamModuleArgValue(const std::string& moduleName,
- const std::string& argName, std::string& argValue);
-
- /** @brief set pam argument value
- * method to set argument value in pam configuration
- *
- * @param[in] moduleName - name of the module in which argument value has
- * to be set
- * @param[in] argName - argument name
- * @param[out] argValue - argument value
- *
- * @return 0 - success state of the function
- */
- int setPamModuleArgValue(const std::string& moduleName,
- const std::string& argName,
- const std::string& argValue);
-
/** @brief get service name
* method to get dbus service name
*
@@ -354,6 +355,9 @@
virtual DbusUserObj getPrivilegeMapperObject(void);
friend class TestUserMgr;
+
+ std::string pamPasswdConfigFile;
+ std::string pamAuthConfigFile;
};
} // namespace user