summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format84
-rw-r--r--Makefile.am14
-rw-r--r--file.hpp113
-rw-r--r--mainapp.cpp15
-rw-r--r--shadowlock.hpp41
-rw-r--r--test/utest.cpp133
-rw-r--r--user.cpp37
-rw-r--r--user.hpp208
-rw-r--r--user_mgr.cpp598
-rw-r--r--user_mgr.hpp207
-rw-r--r--users.cpp146
-rw-r--r--users.hpp111
12 files changed, 1421 insertions, 286 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..37469de
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,84 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: true
+PointerAlignment: Left
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: false
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
diff --git a/Makefile.am b/Makefile.am
index 5d5e14f..b8b753d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,18 +1,26 @@
sbin_PROGRAMS = phosphor-user-manager
-noinst_HEADERS = user.hpp
+noinst_HEADERS = user.hpp user_mgr.hpp users.hpp
phosphor_user_manager_SOURCES = \
user.cpp \
- mainapp.cpp
+ mainapp.cpp \
+ user_mgr.cpp \
+ users.cpp
phosphor_user_manager_LDFLAGS = $(SDBUSPLUS_LIBS) \
$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
$(PHOSPHOR_LOGGING_LIBS) \
+ $(BOOST_CPPFLAGS) \
-lcrypt \
-lstdc++fs
phosphor_user_manager_CXXFLAGS = $(SYSTEMD_CFLAGS) \
$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
- $(PHOSPHOR_LOGGING_CFLAGS)
+ $(PHOSPHOR_LOGGING_CFLAGS) \
+ $(BOOST_CPPFLAGS) \
+ -DBOOST_ALL_NO_LIB \
+ -DBOOST_SYSTEM_NO_DEPRECATED \
+ -DBOOST_ERROR_CODE_HEADER_ONLY
+
SUBDIRS = . test
diff --git a/file.hpp b/file.hpp
index 7b22b3e..34b1422 100644
--- a/file.hpp
+++ b/file.hpp
@@ -15,75 +15,72 @@ namespace fs = std::experimental::filesystem;
*/
class File
{
- private:
- /** @brief handler for operating on file */
- FILE *fp = NULL;
+ private:
+ /** @brief handler for operating on file */
+ FILE* fp = NULL;
- /** @brief File name. Needed in the case where the temp
- * needs to be removed
- */
- const std::string& name;
+ /** @brief File name. Needed in the case where the temp
+ * needs to be removed
+ */
+ const std::string& name;
- /** @brief Should the file be removed at exit */
- bool removeOnExit = false;
+ /** @brief Should the file be removed at exit */
+ bool removeOnExit = false;
- public:
- File() = delete;
- File(const File&) = delete;
- File& operator=(const File&) = delete;
- File(File&&) = delete;
- File& operator=(File&&) = delete;
+ public:
+ File() = delete;
+ File(const File&) = delete;
+ File& operator=(const File&) = delete;
+ File(File&&) = delete;
+ File& operator=(File&&) = delete;
- /** @brief Opens file and uses it to do file operation
- *
- * @param[in] name - File name
- * @param[in] mode - File open mode
- * @param[in] removeOnExit - File to be removed at exit or no
- */
- File(const std::string& name,
- const std::string& mode,
- bool removeOnExit = false) :
- name(name),
- removeOnExit(removeOnExit)
- {
- fp = fopen(name.c_str(), mode.c_str());
- }
+ /** @brief Opens file and uses it to do file operation
+ *
+ * @param[in] name - File name
+ * @param[in] mode - File open mode
+ * @param[in] removeOnExit - File to be removed at exit or no
+ */
+ File(const std::string& name, const std::string& mode,
+ bool removeOnExit = false) :
+ name(name),
+ removeOnExit(removeOnExit)
+ {
+ fp = fopen(name.c_str(), mode.c_str());
+ }
- /** @brief Opens file using provided file descriptor
- *
- * @param[in] fd - File descriptor
- * @param[in] name - File name
- * @param[in] mode - File open mode
- * @param[in] removeOnExit - File to be removed at exit or no
- */
- File(int fd,
- const std::string& name,
- const std::string& mode,
- bool removeOnExit = false) :
- name(name),
- removeOnExit(removeOnExit)
- {
- fp = fdopen(fd, mode.c_str());
- }
+ /** @brief Opens file using provided file descriptor
+ *
+ * @param[in] fd - File descriptor
+ * @param[in] name - File name
+ * @param[in] mode - File open mode
+ * @param[in] removeOnExit - File to be removed at exit or no
+ */
+ File(int fd, const std::string& name, const std::string& mode,
+ bool removeOnExit = false) :
+ name(name),
+ removeOnExit(removeOnExit)
+ {
+ fp = fdopen(fd, mode.c_str());
+ }
- ~File()
+ ~File()
+ {
+ if (fp)
{
- if (fp)
- {
- fclose(fp);
- }
-
- // Needed for exception safety
- if (removeOnExit && fs::exists(name))
- {
- fs::remove(name);
- }
+ fclose(fp);
}
- auto operator()()
+ // Needed for exception safety
+ if (removeOnExit && fs::exists(name))
{
- return fp;
+ fs::remove(name);
}
+ }
+
+ auto operator()()
+ {
+ return fp;
+ }
};
} // namespace user
diff --git a/mainapp.cpp b/mainapp.cpp
index 04c7825..c9da030 100644
--- a/mainapp.cpp
+++ b/mainapp.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include <string>
-#include "user.hpp"
+#include "user_mgr.hpp"
#include "config.h"
// D-Bus root for user manager
@@ -23,22 +23,15 @@ constexpr auto USER_MANAGER_ROOT = "/xyz/openbmc_project/user";
int main(int argc, char** argv)
{
auto bus = sdbusplus::bus::new_default();
-
- // This is hard coded "root" user.
- // TODO: This would need to be changed when the complete
- // user management code is written. May be, have manager
- // create these user objects.
- // Issue: openbmc/openbmc#2299
- auto objPath = std::string{USER_MANAGER_ROOT} + '/' + "root";
-
sdbusplus::server::manager::manager objManager(bus, USER_MANAGER_ROOT);
- phosphor::user::User user(bus, objPath.c_str());
+
+ phosphor::user::UserMgr userMgr(bus, USER_MANAGER_ROOT);
// Claim the bus now
bus.request_name(USER_MANAGER_BUSNAME);
// Wait for client request
- while(true)
+ while (true)
{
// process dbus calls / signals discarding unhandled
bus.process_discard();
diff --git a/shadowlock.hpp b/shadowlock.hpp
index dc17b5a..81903df 100644
--- a/shadowlock.hpp
+++ b/shadowlock.hpp
@@ -2,6 +2,7 @@
#include <stdio.h>
#include <cassert>
+#include <shadow.h>
#include <phosphor-logging/log.hpp>
#include <phosphor-logging/elog.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
@@ -13,8 +14,8 @@ namespace user
namespace shadow
{
-using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
- Error::InternalFailure;
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
using namespace phosphor::logging;
/** @class Lock
@@ -22,29 +23,29 @@ using namespace phosphor::logging;
*/
class Lock
{
- public:
- Lock(const Lock&) = delete;
- Lock& operator=(const Lock&) = delete;
- Lock(Lock&&) = delete;
- Lock& operator=(Lock&&) = delete;
+ public:
+ Lock(const Lock&) = delete;
+ Lock& operator=(const Lock&) = delete;
+ Lock(Lock&&) = delete;
+ Lock& operator=(Lock&&) = delete;
- /** @brief Default constructor that just locks the shadow file */
- Lock()
+ /** @brief Default constructor that just locks the shadow file */
+ Lock()
+ {
+ if (!lckpwdf())
{
- if (!lckpwdf())
- {
- log<level::ERR>("Locking Shadow failed");
- elog<InternalFailure>();
- }
+ log<level::ERR>("Locking Shadow failed");
+ elog<InternalFailure>();
}
- ~Lock()
+ }
+ ~Lock()
+ {
+ if (!ulckpwdf())
{
- if(!ulckpwdf())
- {
- log<level::ERR>("Un-Locking Shadow failed");
- elog<InternalFailure>();
- }
+ log<level::ERR>("Un-Locking Shadow failed");
+ elog<InternalFailure>();
}
+ }
};
} // namespace shadow
diff --git a/test/utest.cpp b/test/utest.cpp
index 33e0576..5e17283 100644
--- a/test/utest.cpp
+++ b/test/utest.cpp
@@ -28,77 +28,70 @@ 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)
+ 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))
{
- // 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();
+ fs::remove(testShadow);
}
- // Gets called as part of each TEST_F destruction
- ~UserTest()
+ if (fs::exists(shadowCompare))
{
- if (fs::exists(testShadow))
- {
- fs::remove(testShadow);
- }
-
- 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, password, salt);
+ 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, password, salt);
+ }
};
/** @brief Makes sure that SHA512 crypt field is extracted
@@ -175,8 +168,12 @@ TEST_F(UserTest, verifyShadowPermission)
// 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{};
+ struct stat shadow
+ {
+ };
+ struct stat compare
+ {
+ };
stat(testShadow, &shadow);
stat(shadowCompare, &compare);
diff --git a/user.cpp b/user.cpp
index 447bfcd..6999a98 100644
--- a/user.cpp
+++ b/user.cpp
@@ -40,10 +40,10 @@ constexpr auto SHADOW_FILE = "/etc/shadow";
constexpr int SALT_LENGTH = 16;
using namespace phosphor::logging;
-using InsufficientPermission = sdbusplus::xyz::openbmc_project::Common::
- Error::InsufficientPermission;
-using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
- Error::InternalFailure;
+using InsufficientPermission =
+ sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
// Sets or updates the password
void User::setPassword(std::string newPassword)
{
@@ -68,15 +68,14 @@ void User::setPassword(std::string newPassword)
}
void User::applyPassword(const std::string& shadowFile,
- const std::string& password,
- const std::string& salt)
+ const std::string& password, const std::string& salt)
{
// Needed by getspnam_r
struct spwd shdp;
struct spwd* pshdp;
// This should be fine even if SHA512 is used.
- std::array<char,1024> buffer{};
+ std::array<char, 1024> buffer{};
// Open the shadow file for reading
phosphor::user::File shadow(shadowFile, "r");
@@ -87,10 +86,10 @@ void User::applyPassword(const std::string& shadowFile,
// open temp shadow file, by suffixing random name in shadow file name.
std::vector<char> tempFileName(shadowFile.begin(), shadowFile.end());
- std::vector<char> fileTemplate = {
- '_', '_', 'X', 'X', 'X', 'X', 'X', 'X', '\0' };
- tempFileName.insert(
- tempFileName.end(), fileTemplate.begin(), fileTemplate.end());
+ std::vector<char> fileTemplate = {'_', '_', 'X', 'X', 'X',
+ 'X', 'X', 'X', '\0'};
+ tempFileName.insert(tempFileName.end(), fileTemplate.begin(),
+ fileTemplate.end());
int fd = mkstemp(tempFileName.data());
if (fd == -1)
@@ -112,7 +111,9 @@ void User::applyPassword(const std::string& shadowFile,
// Change the permission of this new temp file
// to be same as shadow so that it's secure
- struct stat st{};
+ struct stat st
+ {
+ };
auto r = fstat(fileno((shadow)()), &st);
if (r < 0)
{
@@ -184,15 +185,13 @@ void User::raiseException(int errNo, const std::string& errMsg)
}
else
{
- log<level::ERR>(errMsg.c_str(),
- entry("USER=%s",user.c_str()),
- entry("ERRNO=%d", errNo));
+ log<level::ERR>(errMsg.c_str(), entry("USER=%s", user.c_str()),
+ entry("ERRNO=%d", errNo));
elog<InternalFailure>();
}
}
-std::string User::hashPassword(char* spPwdp,
- const std::string& password,
+std::string User::hashPassword(char* spPwdp, const std::string& password,
const std::string& salt)
{
// Parse and get crypt algo
@@ -200,7 +199,7 @@ std::string User::hashPassword(char* spPwdp,
if (cryptAlgo.empty())
{
log<level::ERR>("Error finding crypt algo",
- entry("USER=%s",user.c_str()));
+ entry("USER=%s", user.c_str()));
elog<InternalFailure>();
}
@@ -226,7 +225,7 @@ const std::string User::randomString(int length)
// Standard mersenne_twister_engine seeded with rd()
std::mt19937 gen(rd());
- std::uniform_int_distribution<> dis(0, set.size()-1);
+ std::uniform_int_distribution<> dis(0, set.size() - 1);
for (int count = 0; count < length; count++)
{
// Use dis to transform the random unsigned int generated by
diff --git a/user.hpp b/user.hpp
index ca8673f..2e57702 100644
--- a/user.hpp
+++ b/user.hpp
@@ -23,113 +23,107 @@ using Interface = sdbusplus::server::object::object<Base::Password>;
*/
class User : public Interface
{
- public:
- User() = delete;
- ~User() = default;
- User(const User&) = delete;
- User& operator=(const User&) = delete;
- User(User&&) = delete;
- User& operator=(User&&) = delete;
-
- /** @brief Constructs User object.
- *
- * @param[in] bus - sdbusplus handler
- * @param[in] path - D-Bus path
- */
- User(sdbusplus::bus::bus& bus, const char* path)
- : Interface(bus, path),
- bus(bus),
- path(path),
- user(fs::path(path).filename())
- {
- // Do nothing
- }
-
- /** @brief user password set method. If this is called for
- * a user ID that already has the password, the password
- * would be updated, else password would be created.
- * Since this needs an already authenticated session,
- * old password is not needed.
- *
- * @param[in] newPassword - New password
- */
- void setPassword(std::string newPassword) override;
-
-
- private:
- /** @brief sdbusplus handler */
- sdbusplus::bus::bus& bus;
-
- /** @brief object path */
- const std::string& path;
-
- /** @brief User id extracted from object path */
- const std::string user;
-
- /** @brief Returns a random string from set [A-Za-z0-9./]
- * of length size
- *
- * @param[in] numChars - length of string
- */
- static const std::string randomString(int length);
-
- /** @brief Returns password hash created with crypt algo,
- * salt and password
- *
- * @param[in] spPwdp - sp_pwdp of struct spwd
- * @param[in] password - clear text password
- * @param[in] salt - Random salt
- */
- std::string hashPassword(char* spPwdp,
- const std::string& password,
- const std::string& salt);
-
- /** @brief Extracts crypto number from the shadow entry for user
- *
- * @param[in] spPwdp - sp_pwdp of struct spwd
- */
- static CryptAlgo getCryptField(char* spPwdp);
-
- /** @brief Generates one-way hash based on salt and password
- *
- * @param[in] password - clear text password
- * @param[in] salt - Combination of crypto method and salt
- * Eg: $1$HELLO$, where in 1 is crypto method
- * and HELLO is salt
- */
- static std::string generateHash(const std::string& password,
- const std::string& salt);
-
- /** @brief Returns salt string with $ delimiter.
- * Eg: If crypt is 1 and salt is HELLO, returns $1$HELLO$
- *
- * @param[in] crypt - Crypt number in string
- * @param[in] salt - salt
- */
- static std::string getSaltString(const std::string& crypt,
- const std::string& salt);
-
- /** @brief Applies the password for a given user.
- * Writes shadow entries into a temp file
- *
- * @param[in] shadowFile - shadow password file
- * @param[in] password - clear text password
- * @param[in] salt - salt
- */
- void applyPassword(const std::string& shadowFile,
- const std::string& password,
- const std::string& salt);
-
- /** @brief Wrapper for raising exception
- *
- * @param[in] errNo - errno
- * @param[in] errMsg - Error message
- */
- void raiseException(int errNo,
- const std::string& errMsg);
-
- /** @brief For enabling test cases */
- friend class UserTest;
+ public:
+ User() = delete;
+ ~User() = default;
+ User(const User&) = delete;
+ User& operator=(const User&) = delete;
+ User(User&&) = delete;
+ User& operator=(User&&) = delete;
+
+ /** @brief Constructs User object.
+ *
+ * @param[in] bus - sdbusplus handler
+ * @param[in] path - D-Bus path
+ */
+ User(sdbusplus::bus::bus& bus, const char* path) :
+ Interface(bus, path), bus(bus), path(path),
+ user(fs::path(path).filename())
+ {
+ // Do nothing
+ }
+
+ /** @brief user password set method. If this is called for
+ * a user ID that already has the password, the password
+ * would be updated, else password would be created.
+ * Since this needs an already authenticated session,
+ * old password is not needed.
+ *
+ * @param[in] newPassword - New password
+ */
+ void setPassword(std::string newPassword) override;
+
+ private:
+ /** @brief sdbusplus handler */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief object path */
+ const std::string& path;
+
+ /** @brief User id extracted from object path */
+ const std::string user;
+
+ /** @brief Returns a random string from set [A-Za-z0-9./]
+ * of length size
+ *
+ * @param[in] numChars - length of string
+ */
+ static const std::string randomString(int length);
+
+ /** @brief Returns password hash created with crypt algo,
+ * salt and password
+ *
+ * @param[in] spPwdp - sp_pwdp of struct spwd
+ * @param[in] password - clear text password
+ * @param[in] salt - Random salt
+ */
+ std::string hashPassword(char* spPwdp, const std::string& password,
+ const std::string& salt);
+
+ /** @brief Extracts crypto number from the shadow entry for user
+ *
+ * @param[in] spPwdp - sp_pwdp of struct spwd
+ */
+ static CryptAlgo getCryptField(char* spPwdp);
+
+ /** @brief Generates one-way hash based on salt and password
+ *
+ * @param[in] password - clear text password
+ * @param[in] salt - Combination of crypto method and salt
+ * Eg: $1$HELLO$, where in 1 is crypto method
+ * and HELLO is salt
+ */
+ static std::string generateHash(const std::string& password,
+ const std::string& salt);
+
+ /** @brief Returns salt string with $ delimiter.
+ * Eg: If crypt is 1 and salt is HELLO, returns $1$HELLO$
+ *
+ * @param[in] crypt - Crypt number in string
+ * @param[in] salt - salt
+ */
+ static std::string getSaltString(const std::string& crypt,
+ const std::string& salt);
+
+ /** @brief Applies the password for a given user.
+ * Writes shadow entries into a temp file
+ *
+ * @param[in] shadowFile - shadow password file
+ * @param[in] password - clear text password
+ * @param[in] salt - salt
+ */
+ void applyPassword(const std::string& shadowFile,
+ const std::string& password, const std::string& salt);
+
+ /** @brief Wrapper for raising exception
+ *
+ * @param[in] errNo - errno
+ * @param[in] errMsg - Error message
+ */
+ void raiseException(int errNo, const std::string& errMsg);
+
+ /** @brief For enabling test cases */
+ friend class UserTest;
};
} // namespace user
diff --git a/user_mgr.cpp b/user_mgr.cpp
new file mode 100644
index 0000000..3987088
--- /dev/null
+++ b/user_mgr.cpp
@@ -0,0 +1,598 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <shadow.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fstream>
+#include <grp.h>
+#include <pwd.h>
+#include <regex>
+#include <algorithm>
+#include <numeric>
+#include <boost/process/child.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/User/Common/error.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include "shadowlock.hpp"
+#include "file.hpp"
+#include "user_mgr.hpp"
+#include "users.hpp"
+#include "config.h"
+
+namespace phosphor
+{
+namespace user
+{
+
+static constexpr const char *passwdFileName = "/etc/passwd";
+static constexpr size_t ipmiMaxUsers = 15;
+static constexpr size_t ipmiMaxUserNameLen = 16;
+static constexpr size_t systemMaxUserNameLen = 30;
+static constexpr size_t maxSystemUsers = 30;
+static constexpr const char *grpSsh = "ssh";
+
+using namespace phosphor::logging;
+using InsufficientPermission =
+ sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using InvalidArgument =
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+using UserNameExists =
+ sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
+using UserNameDoesNotExist =
+ sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
+using UserNameGroupFail =
+ sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
+
+using NoResource =
+ sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
+
+using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+template <typename... ArgTypes>
+static void executeCmd(const char *path, ArgTypes &&... tArgs)
+{
+ boost::process::child execProg(path, const_cast<char *>(tArgs)...);
+ execProg.wait();
+ int retCode = execProg.exit_code();
+ if (retCode)
+ {
+ log<level::ERR>("Command execution failed", entry("PATH=%s", path),
+ entry("RETURN_CODE:%d", retCode));
+ elog<InternalFailure>();
+ }
+ return;
+}
+
+static std::string getCSVFromVector(std::vector<std::string> vec)
+{
+ switch (vec.size())
+ {
+ case 0:
+ {
+ return "";
+ }
+ break;
+
+ case 1:
+ {
+ return std::string{vec[0]};
+ }
+ break;
+
+ default:
+ {
+ return std::accumulate(
+ std::next(vec.begin()), vec.end(), vec[0],
+ [](std::string a, std::string b) { return a + ',' + b; });
+ }
+ }
+}
+
+static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr)
+{
+ std::string::size_type delStrPos = csvStr.find(delStr);
+ if (delStrPos != std::string::npos)
+ {
+ // need to also delete the comma char
+ if (delStrPos == 0)
+ {
+ csvStr.erase(delStrPos, delStr.size() + 1);
+ }
+ else
+ {
+ csvStr.erase(delStrPos - 1, delStr.size() + 1);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool UserMgr::isUserExist(const std::string &userName)
+{
+ if (userName.empty())
+ {
+ log<level::ERR>("User name is empty");
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
+ Argument::ARGUMENT_VALUE("Null"));
+ }
+ if (usersList.find(userName) == usersList.end())
+ {
+ return false;
+ }
+ return true;
+}
+
+void UserMgr::throwForUserDoesNotExist(const std::string &userName)
+{
+ if (isUserExist(userName) == false)
+ {
+ log<level::ERR>("User does not exist",
+ entry("USER_NAME=%s", userName.c_str()));
+ elog<UserNameDoesNotExist>();
+ }
+}
+
+void UserMgr::throwForUserExists(const std::string &userName)
+{
+ if (isUserExist(userName) == true)
+ {
+ log<level::ERR>("User already exists",
+ entry("USER_NAME=%s", userName.c_str()));
+ elog<UserNameExists>();
+ }
+}
+
+void UserMgr::throwForUserNameConstraints(
+ const std::string &userName, const std::vector<std::string> &groupNames)
+{
+ if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
+ groupNames.end())
+ {
+ if (userName.length() > ipmiMaxUserNameLen)
+ {
+ log<level::ERR>("IPMI user name length limitation",
+ entry("SIZE=%d", userName.length()));
+ elog<UserNameGroupFail>(
+ xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
+ "IPMI length"));
+ }
+ }
+ if (userName.length() > systemMaxUserNameLen)
+ {
+ log<level::ERR>("User name length limitation",
+ entry("SIZE=%d", userName.length()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
+ Argument::ARGUMENT_VALUE("Invalid length"));
+ }
+ if (!std::regex_match(userName.c_str(),
+ std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
+ {
+ log<level::ERR>("Invalid user name",
+ entry("USER_NAME=%s", userName.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
+ Argument::ARGUMENT_VALUE("Invalid data"));
+ }
+}
+
+void UserMgr::throwForMaxGrpUserCount(
+ const std::vector<std::string> &groupNames)
+{
+ if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
+ groupNames.end())
+ {
+ if (getIpmiUsersCount() >= ipmiMaxUsers)
+ {
+ log<level::ERR>("IPMI user limit reached");
+ elog<NoResource>(
+ xyz::openbmc_project::User::Common::NoResource::REASON(
+ "ipmi user count reached"));
+ }
+ }
+ else
+ {
+ if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
+ (maxSystemUsers - ipmiMaxUsers))
+ {
+ log<level::ERR>("Non-ipmi User limit reached");
+ elog<NoResource>(
+ xyz::openbmc_project::User::Common::NoResource::REASON(
+ "Non-ipmi user count reached"));
+ }
+ }
+ return;
+}
+
+void UserMgr::throwForInvalidPrivilege(const std::string &priv)
+{
+ if (!priv.empty() &&
+ (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
+ {
+ log<level::ERR>("Invalid privilege");
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
+ Argument::ARGUMENT_VALUE(priv.c_str()));
+ }
+}
+
+void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames)
+{
+ for (auto &group : groupNames)
+ {
+ if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
+ groupsMgr.end())
+ {
+ log<level::ERR>("Invalid Group Name listed");
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
+ Argument::ARGUMENT_VALUE(group.c_str()));
+ }
+ }
+}
+
+void UserMgr::createUser(std::string userName,
+ std::vector<std::string> groupNames, std::string priv,
+ bool enabled)
+{
+ throwForInvalidPrivilege(priv);
+ throwForInvalidGroups(groupNames);
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ throwForUserExists(userName);
+ throwForUserNameConstraints(userName, groupNames);
+ throwForMaxGrpUserCount(groupNames);
+
+ std::string groups = getCSVFromVector(groupNames);
+ bool sshRequested = removeStringFromCSV(groups, grpSsh);
+
+ // treat privilege as a group - This is to avoid using different file to
+ // store the same.
+ if (groups.size() != 0)
+ {
+ groups += ",";
+ }
+ groups += priv;
+
+ try
+ {
+ executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
+ "-M", "-N", "-s",
+ (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
+ (enabled ? "" : "1970-01-02"));
+ }
+ catch (const InternalFailure &e)
+ {
+ log<level::ERR>("Unable to create new user");
+ elog<InternalFailure>();
+ }
+
+ // Add the users object before sending out the signal
+ std::string userObj = std::string(usersObjPath) + "/" + userName;
+ std::sort(groupNames.begin(), groupNames.end());
+ usersList.emplace(
+ userName, std::move(std::make_unique<phosphor::user::Users>(
+ bus, userObj.c_str(), groupNames, priv, enabled, *this)));
+
+ log<level::INFO>("User created successfully",
+ entry("USER_NAME=%s", userName.c_str()));
+ return;
+}
+
+void UserMgr::deleteUser(std::string userName)
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ throwForUserDoesNotExist(userName);
+ try
+ {
+ executeCmd("/usr/sbin/userdel", userName.c_str());
+ }
+ catch (const InternalFailure &e)
+ {
+ log<level::ERR>("User delete failed",
+ entry("USER_NAME=%s", userName.c_str()));
+ elog<InternalFailure>();
+ }
+
+ usersList.erase(userName);
+
+ log<level::INFO>("User deleted successfully",
+ entry("USER_NAME=%s", userName.c_str()));
+ return;
+}
+
+void UserMgr::renameUser(std::string userName, std::string newUserName)
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ throwForUserDoesNotExist(userName);
+ throwForUserExists(newUserName);
+ throwForUserNameConstraints(newUserName,
+ usersList[userName].get()->userGroups());
+ try
+ {
+ executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
+ userName.c_str());
+ }
+ catch (const InternalFailure &e)
+ {
+ log<level::ERR>("User rename failed",
+ entry("USER_NAME=%s", userName.c_str()));
+ elog<InternalFailure>();
+ }
+ const auto &user = usersList[userName];
+ std::string priv = user.get()->userPrivilege();
+ std::vector<std::string> groupNames = user.get()->userGroups();
+ bool enabled = user.get()->userEnabled();
+ std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
+ // Special group 'ipmi' needs a way to identify user renamed, in order to
+ // update encrypted password. It can't rely only on InterfacesRemoved &
+ // InterfacesAdded. So first send out userRenamed signal.
+ this->userRenamed(userName, newUserName);
+ usersList.erase(userName);
+ usersList.emplace(
+ newUserName,
+ std::move(std::make_unique<phosphor::user::Users>(
+ bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
+ return;
+}
+
+void UserMgr::updateGroupsAndPriv(const std::string &userName,
+ const std::vector<std::string> &groupNames,
+ const std::string &priv)
+{
+ throwForInvalidPrivilege(priv);
+ throwForInvalidGroups(groupNames);
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ throwForUserDoesNotExist(userName);
+ const std::vector<std::string> &oldGroupNames =
+ usersList[userName].get()->userGroups();
+ std::vector<std::string> groupDiff;
+ // Note: already dealing with sorted group lists.
+ std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
+ groupNames.begin(), groupNames.end(),
+ std::back_inserter(groupDiff));
+ if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
+ groupDiff.end())
+ {
+ throwForUserNameConstraints(userName, groupNames);
+ throwForMaxGrpUserCount(groupNames);
+ }
+
+ std::string groups = getCSVFromVector(groupNames);
+ bool sshRequested = removeStringFromCSV(groups, grpSsh);
+
+ // treat privilege as a group - This is to avoid using different file to
+ // store the same.
+ if (groups.size() != 0)
+ {
+ groups += ",";
+ }
+ groups += priv;
+ try
+ {
+ executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
+ "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
+ }
+ catch (const InternalFailure &e)
+ {
+ log<level::ERR>("Unable to modify user privilege / groups");
+ elog<InternalFailure>();
+ }
+
+ log<level::INFO>("User groups / privilege updated successfully",
+ entry("USER_NAME=%s", userName.c_str()));
+ return;
+}
+
+void UserMgr::userEnable(const std::string &userName, bool enabled)
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ throwForUserDoesNotExist(userName);
+ try
+ {
+ executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
+ (enabled ? "" : "1970-01-02"));
+ }
+ catch (const InternalFailure &e)
+ {
+ log<level::ERR>("Unable to modify user enabled state");
+ elog<InternalFailure>();
+ }
+
+ log<level::INFO>("User enabled/disabled state updated successfully",
+ entry("USER_NAME=%s", userName.c_str()),
+ entry("ENABLED=%d", enabled));
+ return;
+}
+
+UserSSHLists UserMgr::getUserAndSshGrpList()
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+
+ std::vector<std::string> userList;
+ std::vector<std::string> sshUsersList;
+ struct passwd pw, *pwp = nullptr;
+ std::array<char, 1024> buffer{};
+
+ phosphor::user::File passwd(passwdFileName, "r");
+ if ((passwd)() == NULL)
+ {
+ log<level::ERR>("Error opening the passwd file");
+ elog<InternalFailure>();
+ }
+
+ while (true)
+ {
+ auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
+ &pwp);
+ if ((r != 0) || (pwp == NULL))
+ {
+ // Any error, break the loop.
+ break;
+ }
+ // All users whose UID >= 1000 and < 65534
+ if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
+ {
+ std::string userName(pwp->pw_name);
+ userList.emplace_back(userName);
+
+ // ssh doesn't have separate group. Check login shell entry to
+ // get all users list which are member of ssh group.
+ std::string loginShell(pwp->pw_shell);
+ if (loginShell == "/bin/sh")
+ {
+ sshUsersList.emplace_back(userName);
+ }
+ }
+ }
+ endpwent();
+ return std::make_pair(std::move(userList), std::move(sshUsersList));
+}
+
+size_t UserMgr::getIpmiUsersCount()
+{
+ std::vector<std::string> userList = getUsersInGroup("ipmi");
+ return userList.size();
+}
+
+bool UserMgr::isUserEnabled(const std::string &userName)
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ std::array<char, 4096> buffer{};
+ struct spwd spwd;
+ struct spwd *resultPtr = nullptr;
+ int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
+ buffer.max_size(), &resultPtr);
+ if (!status && (&spwd == resultPtr))
+ {
+ if (resultPtr->sp_expire >= 0)
+ {
+ return false; // user locked out
+ }
+ return true;
+ }
+ return false; // assume user is disabled for any error.
+}
+
+std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
+{
+ std::vector<std::string> usersInGroup;
+ // Should be more than enough to get the pwd structure.
+ std::array<char, 4096> buffer{};
+ struct group grp;
+ struct group *resultPtr = nullptr;
+
+ int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
+ buffer.max_size(), &resultPtr);
+
+ if (!status && (&grp == resultPtr))
+ {
+ for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
+ {
+ usersInGroup.emplace_back(*(grp.gr_mem));
+ }
+ }
+ else
+ {
+ log<level::ERR>("Group not found",
+ entry("GROUP=%s", groupName.c_str()));
+ // Don't throw error, just return empty userList - fallback
+ }
+ return usersInGroup;
+}
+
+void UserMgr::initUserObjects(void)
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+ std::vector<std::string> userNameList;
+ std::vector<std::string> sshGrpUsersList;
+ UserSSHLists userSSHLists = getUserAndSshGrpList();
+ userNameList = std::move(userSSHLists.first);
+ sshGrpUsersList = std::move(userSSHLists.second);
+
+ if (!userNameList.empty())
+ {
+ std::map<std::string, std::vector<std::string>> groupLists;
+ for (auto &grp : groupsMgr)
+ {
+ if (grp == grpSsh)
+ {
+ groupLists.emplace(grp, sshGrpUsersList);
+ }
+ else
+ {
+ std::vector<std::string> grpUsersList = getUsersInGroup(grp);
+ groupLists.emplace(grp, grpUsersList);
+ }
+ }
+ for (auto &grp : privMgr)
+ {
+ std::vector<std::string> grpUsersList = getUsersInGroup(grp);
+ groupLists.emplace(grp, grpUsersList);
+ }
+
+ for (auto &user : userNameList)
+ {
+ std::vector<std::string> userGroups;
+ std::string userPriv;
+ for (const auto &grp : groupLists)
+ {
+ std::vector<std::string> tempGrp = grp.second;
+ if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
+ tempGrp.end())
+ {
+ if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
+ privMgr.end())
+ {
+ userPriv = grp.first;
+ }
+ else
+ {
+ userGroups.emplace_back(grp.first);
+ }
+ }
+ }
+ // Add user objects to the Users path.
+ auto objPath = std::string(usersObjPath) + "/" + user;
+ std::sort(userGroups.begin(), userGroups.end());
+ usersList.emplace(user,
+ std::move(std::make_unique<phosphor::user::Users>(
+ bus, objPath.c_str(), userGroups, userPriv,
+ isUserEnabled(user), *this)));
+ }
+ }
+}
+
+UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
+ UserMgrIface(bus, path), bus(bus), path(path)
+{
+ UserMgrIface::allPrivileges(privMgr);
+ std::sort(groupsMgr.begin(), groupsMgr.end());
+ UserMgrIface::allGroups(groupsMgr);
+ initUserObjects();
+}
+
+} // namespace user
+} // namespace phosphor
diff --git a/user_mgr.hpp b/user_mgr.hpp
new file mode 100644
index 0000000..44e14f7
--- /dev/null
+++ b/user_mgr.hpp
@@ -0,0 +1,207 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/User/Manager/server.hpp>
+#include <unordered_map>
+#include "users.hpp"
+
+namespace phosphor
+{
+namespace user
+{
+
+using UserMgrIface = sdbusplus::xyz::openbmc_project::User::server::Manager;
+using UserSSHLists =
+ std::pair<std::vector<std::string>, std::vector<std::string>>;
+/** @class UserMgr
+ * @brief Responsible for managing user accounts over the D-Bus interface.
+ */
+class UserMgr : public UserMgrIface
+{
+ public:
+ UserMgr() = delete;
+ ~UserMgr() = default;
+ UserMgr(const UserMgr &) = delete;
+ UserMgr &operator=(const UserMgr &) = delete;
+ UserMgr(UserMgr &&) = delete;
+ UserMgr &operator=(UserMgr &&) = delete;
+
+ /** @brief Constructs UserMgr object.
+ *
+ * @param[in] bus - sdbusplus handler
+ * @param[in] path - D-Bus path
+ */
+ UserMgr(sdbusplus::bus::bus &bus, const char *path);
+
+ /** @brief create user method.
+ * This method creates a new user as requested
+ *
+ * @param[in] userName - Name of the user which has to be created
+ * @param[in] groupNames - Group names list, to which user has to be added.
+ * @param[in] priv - Privilege of the user.
+ * @param[in] enabled - State of the user enabled / disabled.
+ */
+ void createUser(std::string userName, std::vector<std::string> groupNames,
+ std::string priv, bool enabled) override;
+
+ /** @brief rename user method.
+ * This method renames the user as requested
+ *
+ * @param[in] userName - current name of the user
+ * @param[in] newUserName - new user name to which it has to be renamed.
+ */
+ void renameUser(std::string userName, std::string newUserName) override;
+
+ /** @brief delete user method.
+ * This method deletes the user as requested
+ *
+ * @param[in] userName - Name of the user which has to be deleted
+ */
+ void deleteUser(std::string userName);
+
+ /** @brief Update user groups & privilege.
+ * This method updates user groups & privilege
+ *
+ * @param[in] userName - user name, for which update is requested
+ * @param[in] groupName - Group to be updated..
+ * @param[in] priv - Privilege to be updated.
+ */
+ void updateGroupsAndPriv(const std::string &userName,
+ const std::vector<std::string> &groups,
+ const std::string &priv);
+
+ /** @brief Update user enabled state.
+ * This method enables / disables user
+ *
+ * @param[in] userName - user name, for which update is requested
+ * @param[in] enabled - enable / disable the user
+ */
+ void userEnable(const std::string &userName, bool enabled);
+
+ private:
+ /** @brief sdbusplus handler */
+ sdbusplus::bus::bus &bus;
+
+ /** @brief object path */
+ const std::string path;
+
+ /** @brief privilege manager container */
+ std::vector<std::string> privMgr = {"priv-admin", "priv-operator",
+ "priv-user", "priv-callback"};
+
+ /** @brief groups manager container */
+ std::vector<std::string> groupsMgr = {"web", "redfish", "ipmi", "ssh"};
+
+ /** @brief map container to hold users object */
+ using UserName = std::string;
+ std::unordered_map<UserName, std::unique_ptr<phosphor::user::Users>>
+ usersList;
+
+ /** @brief get users in group
+ * method to get group user list
+ *
+ * @param[in] groupName - group name
+ *
+ * @return userList - list of users in the group.
+ */
+ std::vector<std::string> getUsersInGroup(const std::string &groupName);
+
+ /** @brief get user & SSH users list
+ * method to get the users and ssh users list.
+ *
+ *@return - vector of User & SSH user lists
+ */
+ UserSSHLists getUserAndSshGrpList(void);
+
+ /** @brief check for user presence
+ * method to check for user existence
+ *
+ * @param[in] userName - name of the user
+ * @return -true if user exists and false if not.
+ */
+ bool isUserExist(const std::string &userName);
+
+ /** @brief check user exists
+ * method to check whether user exist, and throw if not.
+ *
+ * @param[in] userName - name of the user
+ */
+ void throwForUserDoesNotExist(const std::string &userName);
+
+ /** @brief check user does not exist
+ * method to check whether does not exist, and throw if exists.
+ *
+ * @param[in] userName - name of the user
+ */
+ void throwForUserExists(const std::string &userName);
+
+ /** @brief check user name constraints
+ * method to check user name constraints and throw if failed.
+ *
+ * @param[in] userName - name of the user
+ * @param[in] groupNames - user groups
+ */
+ void
+ throwForUserNameConstraints(const std::string &userName,
+ const std::vector<std::string> &groupNames);
+
+ /** @brief check group user count
+ * method to check max group user count, and throw if limit reached
+ *
+ * @param[in] groupNames - group name
+ */
+ void throwForMaxGrpUserCount(const std::vector<std::string> &groupNames);
+
+ /** @brief check for valid privielge
+ * method to check valid privilege, and throw if invalid
+ *
+ * @param[in] priv - privilege of the user
+ */
+ void throwForInvalidPrivilege(const std::string &priv);
+
+ /** @brief check for valid groups
+ * method to check valid groups, and throw if invalid
+ *
+ * @param[in] groupNames - user groups
+ */
+ void throwForInvalidGroups(const std::vector<std::string> &groupName);
+
+ /** @brief get user enabled state
+ * method to get user enabled state.
+ *
+ * @param[in] userName - name of the user
+ * @return - user enabled status (true/false)
+ */
+ bool isUserEnabled(const std::string &userName);
+
+ /** @brief initialize the user manager objects
+ * method to initialize the user manager objects accordingly
+ *
+ */
+ void initUserObjects(void);
+
+ /** @brief get IPMI user count
+ * method to get IPMI user count
+ *
+ * @return - returns user count
+ */
+ size_t getIpmiUsersCount(void);
+};
+
+} // namespace user
+} // namespace phosphor
diff --git a/users.cpp b/users.cpp
new file mode 100644
index 0000000..c904916
--- /dev/null
+++ b/users.cpp
@@ -0,0 +1,146 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/User/Common/error.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include "user_mgr.hpp"
+#include "users.hpp"
+#include "config.h"
+
+namespace phosphor
+{
+namespace user
+{
+
+using namespace phosphor::logging;
+using InsufficientPermission =
+ sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using InvalidArgument =
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+using NoResource =
+ sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
+
+using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+/** @brief Constructs UserMgr object.
+ *
+ * @param[in] bus - sdbusplus handler
+ * @param[in] path - D-Bus path
+ * @param[in] groups - users group list
+ * @param[in] priv - user privilege
+ * @param[in] enabled - user enabled state
+ * @param[in] parent - user manager - parent object
+ */
+Users::Users(sdbusplus::bus::bus &bus, const char *path,
+ std::vector<std::string> groups, std::string priv, bool enabled,
+ UserMgr &parent) :
+ UsersIface(bus, path, true),
+ DeleteIface(bus, path),
+ userName(std::experimental::filesystem::path(path).filename()),
+ manager(parent)
+{
+ UsersIface::userPrivilege(priv, true);
+ UsersIface::userGroups(groups, true);
+ UsersIface::userEnabled(enabled, true);
+ UsersIface::emit_object_added();
+}
+
+/** @brief delete user method.
+ * This method deletes the user as requested
+ *
+ */
+void Users::delete_(void)
+{
+ manager.deleteUser(userName);
+}
+
+/** @brief update user privilege
+ *
+ * @param[in] value - User privilege
+ */
+std::string Users::userPrivilege(std::string value)
+{
+ if (value == UsersIface::userPrivilege())
+ {
+ return value;
+ }
+ manager.updateGroupsAndPriv(userName, UsersIface::userGroups(), value);
+ return UsersIface::userPrivilege(value);
+}
+
+/** @brief list user privilege
+ *
+ */
+std::string Users::userPrivilege(void) const
+{
+ return UsersIface::userPrivilege();
+}
+
+/** @brief update user groups
+ *
+ * @param[in] value - User groups
+ */
+std::vector<std::string> Users::userGroups(std::vector<std::string> value)
+{
+ if (value == UsersIface::userGroups())
+ {
+ return value;
+ }
+ std::sort(value.begin(), value.end());
+ manager.updateGroupsAndPriv(userName, value, UsersIface::userPrivilege());
+ return UsersIface::userGroups(value);
+}
+
+/** @brief list user groups
+ *
+ */
+std::vector<std::string> Users::userGroups(void) const
+{
+ return UsersIface::userGroups();
+}
+
+/** @brief lists user enabled state
+ *
+ */
+bool Users::userEnabled(void) const
+{
+ return UsersIface::userEnabled();
+}
+
+/** @brief update user enabled state
+ *
+ * @param[in] value - bool value
+ */
+bool Users::userEnabled(bool value)
+{
+ if (value == UsersIface::userEnabled())
+ {
+ return value;
+ }
+ manager.userEnable(userName, value);
+ return UsersIface::userEnabled(value);
+}
+
+} // namespace user
+} // namespace phosphor
diff --git a/users.hpp b/users.hpp
new file mode 100644
index 0000000..84a0f86
--- /dev/null
+++ b/users.hpp
@@ -0,0 +1,111 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <experimental/filesystem>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/User/Attributes/server.hpp>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
+
+namespace phosphor
+{
+namespace user
+{
+
+namespace Base = sdbusplus::xyz::openbmc_project;
+using UsersIface =
+ sdbusplus::server::object::object<Base::User::server::Attributes>;
+using DeleteIface =
+ sdbusplus::server::object::object<Base::Object::server::Delete>;
+
+// Place where all user objects has to be created
+constexpr auto usersObjPath = "/xyz/openbmc_project/user";
+
+class UserMgr; // Forward declaration for UserMgr.
+
+/** @class Users
+ * @brief Lists User objects and it's properties
+ */
+class Users : public UsersIface, DeleteIface
+{
+ public:
+ Users() = delete;
+ ~Users() = default;
+ Users(const Users &) = delete;
+ Users &operator=(const Users &) = delete;
+ Users(Users &&) = delete;
+ Users &operator=(Users &&) = delete;
+
+ /** @brief Constructs UserMgr object.
+ *
+ * @param[in] bus - sdbusplus handler
+ * @param[in] path - D-Bus path
+ * @param[in] groups - users group list
+ * @param[in] priv - users privilege
+ * @param[in] enabled - user enabled state
+ * @param[in] parent - user manager - parent object
+ */
+ Users(sdbusplus::bus::bus &bus, const char *path,
+ std::vector<std::string> groups, std::string priv, bool enabled,
+ UserMgr &parent);
+
+ /** @brief delete user method.
+ * This method deletes the user as requested
+ *
+ */
+ void delete_(void) override;
+
+ /** @brief update user privilege
+ *
+ * @param[in] value - User privilege
+ */
+ std::string userPrivilege(std::string value) override;
+
+ /** @brief lists user privilege
+ *
+ */
+ std::string userPrivilege(void) const override;
+
+ /** @brief update user groups
+ *
+ * @param[in] value - User groups
+ */
+ std::vector<std::string>
+ userGroups(std::vector<std::string> value) override;
+
+ /** @brief list user groups
+ *
+ */
+ std::vector<std::string> userGroups(void) const override;
+
+ /** @brief lists user enabled state
+ *
+ */
+ bool userEnabled(void) const override;
+
+ /** @brief update user enabled state
+ *
+ * @param[in] value - bool value
+ */
+ bool userEnabled(bool value) override;
+
+ private:
+ std::string userName;
+ UserMgr &manager;
+};
+
+} // namespace user
+} // namespace phosphor
OpenPOWER on IntegriCloud