diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | configure.ac | 25 | ||||
-rw-r--r-- | test/Makefile.am | 27 | ||||
-rw-r--r-- | test/utest.cpp | 210 | ||||
-rw-r--r-- | user.hpp | 4 |
5 files changed, 266 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am index 1e1db3e..5d5e14f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,3 +15,4 @@ phosphor_user_manager_LDFLAGS = $(SDBUSPLUS_LIBS) \ phosphor_user_manager_CXXFLAGS = $(SYSTEMD_CFLAGS) \ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \ $(PHOSPHOR_LOGGING_CFLAGS) +SUBDIRS = . test diff --git a/configure.ac b/configure.ac index c6bb3fe..6d41a5d 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,29 @@ AC_ARG_VAR(DEFAULT_CRYPT_ALGO, [The default crypt algorithm if one not found in AS_IF([test "x$DEFAULT_CRYPT_ALGO" == "x"], [DEFAULT_CRYPT_ALGO="1"]) AC_DEFINE_UNQUOTED([DEFAULT_CRYPT_ALGO], ["$DEFAULT_CRYPT_ALGO"], [The default crypt algorithm if one not found in shadow]) +# Check/set gtest specific functions. +AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"]) +AC_SUBST(GTEST_CPPFLAGS) + +# Test cases require SDK so only build if we're told to (and SDK is available) +AC_ARG_ENABLE([oe-sdk], + AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.]) +) +AC_ARG_VAR(OECORE_TARGET_SYSROOT, + [Path to the OE SDK SYSROOT]) +AS_IF([test "x$enable_oe_sdk" == "xyes"], + AS_IF([test "x$OECORE_TARGET_SYSROOT" == "x"], + AC_MSG_ERROR([OECORE_TARGET_SYSROOT must be set with --enable-oe-sdk]) + ) + AC_MSG_NOTICE([Enabling OE-SDK at $OECORE_TARGET_SYSROOT]) + [ + testcase_flags="-Wl,-rpath,\${OECORE_TARGET_SYSROOT}/lib" + testcase_flags="${testcase_flags} -Wl,-rpath,\${OECORE_TARGET_SYSROOT}/usr/lib" + testcase_flags="${testcase_flags} -Wl,-dynamic-linker,`find \${OECORE_TARGET_SYSROOT}/lib/ld-*.so | sort -r -n | head -n1`" + ] + AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags]) +) + # Checks for typedefs, structures, and compiler characteristics. AX_CXX_COMPILE_STDCXX_14([noext]) AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS]) @@ -34,5 +57,5 @@ AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS]) LT_INIT # Create configured output -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile test/Makefile]) AC_OUTPUT diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..3acdaab --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,27 @@ +AM_CPPFLAGS = -I$(top_srcdir) + +# Run all 'check' test programs +TESTS = $(check_PROGRAMS) + +# Build/add utest to test suite +check_PROGRAMS = utest +utest_CPPFLAGS = -Igtest \ + $(GTEST_CPPFLAGS) \ + $(AM_CPPFLAGS) \ + $(PHOSPHOR_LOGGING_CFLAGS) \ + $(SDBUSPLUS_CFLAGS) + +utest_CXXFLAGS = $(PTHREAD_CFLAGS) + +utest_LDFLAGS = -lgtest_main \ + -lgtest \ + $(PTHREAD_LIBS) \ + $(OESDK_TESTCASE_FLAGS) \ + $(PHOSPHOR_DBUS_INTERFACES_LIBS) \ + $(PHOSPHOR_LOGGING_LIBS) \ + $(SDBUSPLUS_LIBS) \ + -lcrypt \ + -lstdc++fs + +utest_SOURCES = utest.cpp +utest_LDADD = $(top_builddir)/user.o diff --git a/test/utest.cpp b/test/utest.cpp new file mode 100644 index 0000000..bdca968 --- /dev/null +++ b/test/utest.cpp @@ -0,0 +1,210 @@ +#include <sys/stat.h> +#include <string> +#include <fstream> +#include <experimental/filesystem> +#include <gtest/gtest.h> +#include <sdbusplus/bus.hpp> +#include "user.hpp" +namespace phosphor +{ +namespace user +{ + +namespace fs = std::experimental::filesystem; + +constexpr auto path = "/dummy/user"; +constexpr auto testShadow = "/tmp/__tshadow__"; +constexpr auto shadowCopy = "/tmp/__tshadowCopy__"; +constexpr auto shadowCompare = "/tmp/__tshadowCompare__"; + +// New password +constexpr auto password = "passw0rd"; + +constexpr auto MD5 = "1"; +constexpr auto SHA512 = "6"; +constexpr auto salt = "1G.cK/YP"; + +// Example entry matching /etc/shadow structure +constexpr auto spPwdp = "$1$1G.cK/YP$JI5t0oliPxZveXOvLcZ/H.:17344:1:90:7:::"; + +class UserTest : public ::testing::Test +{ + public: + const std::string md5Salt = '$' + std::string(MD5) + '$' + + std::string(salt) + '$'; + const std::string shaSalt = '$' + std::string(SHA512) + '$' + + std::string(salt) + '$'; + + const std::string entry = fs::path(path).filename().string() + + ':' + std::string(spPwdp); + sdbusplus::bus::bus bus; + phosphor::user::User user; + + // Gets called as part of each TEST_F construction + UserTest() + : bus(sdbusplus::bus::new_default()), + user(bus, path) + { + // Create a shadow file entry + std::ofstream file(testShadow); + file << entry; + file.close(); + + // File to compare against + std::ofstream compare(shadowCompare); + compare << entry; + compare.close(); + } + + // Gets called as part of each TEST_F destruction + ~UserTest() + { + if (fs::exists(testShadow)) + { + fs::remove(testShadow); + } + + if (fs::exists(shadowCopy)) + { + fs::remove(shadowCopy); + } + + if (fs::exists(shadowCompare)) + { + fs::remove(shadowCompare); + } + } + + /** @brief wrapper for get crypt field */ + auto getCryptField(char* data) + { + return User::getCryptField( + std::forward<decltype(data)>(data)); + } + + /** @brief wrapper for getSaltString */ + auto getSaltString(const std::string& crypt, + const std::string& salt) + { + return User::getSaltString( + std::forward<decltype(crypt)>(crypt), + std::forward<decltype(salt)>(salt)); + } + + /** @brief wrapper for generateHash */ + auto generateHash(const std::string& password, + const std::string& salt) + { + return User::generateHash( + std::forward<decltype(password)>(password), + std::forward<decltype(salt)>(salt)); + } + + /** @brief Applies the new password */ + auto applyPassword() + { + return user.applyPassword(testShadow, shadowCopy, + password, salt); + } +}; + +/** @brief Makes sure that SHA512 crypt field is extracted + */ +TEST_F(UserTest, sha512GetCryptField) +{ + auto salt = const_cast<char*>(shaSalt.c_str()); + EXPECT_EQ(SHA512, this->getCryptField(salt)); +} + +/** @brief Makes sure that MD5 crypt field is extracted as default + */ +TEST_F(UserTest, md55GetCryptFieldDefault) +{ + auto salt = const_cast<char*>("hello"); + EXPECT_EQ(MD5, this->getCryptField(salt)); +} + +/** @brief Makes sure that MD5 crypt field is extracted + */ +TEST_F(UserTest, md55GetCryptField) +{ + auto salt = const_cast<char*>(md5Salt.c_str()); + EXPECT_EQ(MD5, this->getCryptField(salt)); +} + +/** @brief Makes sure that salt string is put within $$ + */ +TEST_F(UserTest, getSaltString) +{ + EXPECT_EQ(md5Salt, this->getSaltString(MD5, salt)); +} + +/** @brief Makes sure hash is generated correctly + */ +TEST_F(UserTest, generateHash) +{ + std::string sample = crypt(password, md5Salt.c_str()); + std::string actual = generateHash(password, md5Salt); + EXPECT_EQ(sample, actual); +} + +/** @brief Verifies that the correct password is written to file + */ +TEST_F(UserTest, applyPassword) +{ + // Update the password + applyPassword(); + + // Read files and compare + std::ifstream shadow(testShadow); + std::ifstream copy(shadowCompare); + + std::string shadowEntry; + shadow >> shadowEntry; + + std::string shadowCompareEntry; + copy >> shadowCompareEntry; + + EXPECT_EQ(shadowEntry, shadowCompareEntry); +} + +/** @brief Verifies the shadow copy file is removed + */ +TEST_F(UserTest, checkShadowCopyRemove) +{ + // Update the password so that the temp file is in action + applyPassword(); + + // Compare the permission of 2 files + struct stat shadow{}; + struct stat temp{}; + + stat(testShadow, &shadow); + stat(shadowCopy, &temp); + EXPECT_EQ(false, fs::exists(shadowCopy)); +} + +/** @brief Verifies the permissions are correct + */ +TEST_F(UserTest, verifyShadowPermission) +{ + // Change the permission to 400-> -r-------- + chmod(testShadow, S_IRUSR); + chmod(shadowCompare, S_IRUSR); + + // Update the password so that the temp file is in action + applyPassword(); + + // Compare the permission of 2 files. + // file rename would make sure that the permissions + // of old are moved to new + struct stat shadow{}; + struct stat compare{}; + + stat(testShadow, &shadow); + stat(shadowCompare, &compare); + EXPECT_EQ(shadow.st_mode, compare.st_mode); +} + +} // namespace user +} // namespace phosphor @@ -55,6 +55,7 @@ class User : public Interface */ void setPassword(std::string newPassword) override; + private: /** @brief sdbusplus handler */ sdbusplus::bus::bus& bus; @@ -128,6 +129,9 @@ class User : public Interface */ void raiseException(int errNo, const std::string& errMsg); + + /** @brief For enabling test cases */ + friend class UserTest; }; } // namespace user |