From 9f630d9eb0ce1c103532a4304ea080066199094e Mon Sep 17 00:00:00 2001 From: Richard Marian Thomaiyar Date: Thu, 24 May 2018 10:49:10 +0530 Subject: Basic support for User manager service Basic support for User Manager service methods are implemented. Change-Id: Id42432ec6dd421b99971268add931dcd70876f7c Signed-off-by: Richard Marian Thomaiyar --- .clang-format | 84 ++++++++ Makefile.am | 14 +- file.hpp | 113 ++++++----- mainapp.cpp | 15 +- shadowlock.hpp | 41 ++-- test/utest.cpp | 133 +++++++------ user.cpp | 37 ++-- user.hpp | 208 ++++++++++---------- user_mgr.cpp | 598 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ user_mgr.hpp | 207 ++++++++++++++++++++ users.cpp | 146 ++++++++++++++ users.hpp | 111 +++++++++++ 12 files changed, 1421 insertions(+), 286 deletions(-) create mode 100644 .clang-format create mode 100644 user_mgr.cpp create mode 100644 user_mgr.hpp create mode 100644 users.cpp create mode 100644 users.hpp 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 -#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 #include +#include #include #include #include @@ -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("Locking Shadow failed"); - elog(); - } + log("Locking Shadow failed"); + elog(); } - ~Lock() + } + ~Lock() + { + if (!ulckpwdf()) { - if(!ulckpwdf()) - { - log("Un-Locking Shadow failed"); - elog(); - } + log("Un-Locking Shadow failed"); + elog(); } + } }; } // 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(data)); - } - - /** @brief wrapper for getSaltString */ - auto getSaltString(const std::string& crypt, - const std::string& salt) - { - return User::getSaltString( - std::forward(crypt), - std::forward(salt)); - } - - /** @brief wrapper for generateHash */ - auto generateHash(const std::string& password, - const std::string& salt) - { - return User::generateHash( - std::forward(password), - std::forward(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(data)); + } + + /** @brief wrapper for getSaltString */ + auto getSaltString(const std::string& crypt, const std::string& salt) + { + return User::getSaltString(std::forward(crypt), + std::forward(salt)); + } + + /** @brief wrapper for generateHash */ + auto generateHash(const std::string& password, const std::string& salt) + { + return User::generateHash(std::forward(password), + std::forward(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 buffer{}; + std::array 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 tempFileName(shadowFile.begin(), shadowFile.end()); - std::vector fileTemplate = { - '_', '_', 'X', 'X', 'X', 'X', 'X', 'X', '\0' }; - tempFileName.insert( - tempFileName.end(), fileTemplate.begin(), fileTemplate.end()); + std::vector 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(errMsg.c_str(), - entry("USER=%s",user.c_str()), - entry("ERRNO=%d", errNo)); + log(errMsg.c_str(), entry("USER=%s", user.c_str()), + entry("ERRNO=%d", errNo)); elog(); } } -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("Error finding crypt algo", - entry("USER=%s",user.c_str())); + entry("USER=%s", user.c_str())); elog(); } @@ -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; */ 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +static void executeCmd(const char *path, ArgTypes &&... tArgs) +{ + boost::process::child execProg(path, const_cast(tArgs)...); + execProg.wait(); + int retCode = execProg.exit_code(); + if (retCode) + { + log("Command execution failed", entry("PATH=%s", path), + entry("RETURN_CODE:%d", retCode)); + elog(); + } + return; +} + +static std::string getCSVFromVector(std::vector 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("User name is empty"); + elog(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("User does not exist", + entry("USER_NAME=%s", userName.c_str())); + elog(); + } +} + +void UserMgr::throwForUserExists(const std::string &userName) +{ + if (isUserExist(userName) == true) + { + log("User already exists", + entry("USER_NAME=%s", userName.c_str())); + elog(); + } +} + +void UserMgr::throwForUserNameConstraints( + const std::string &userName, const std::vector &groupNames) +{ + if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != + groupNames.end()) + { + if (userName.length() > ipmiMaxUserNameLen) + { + log("IPMI user name length limitation", + entry("SIZE=%d", userName.length())); + elog( + xyz::openbmc_project::User::Common::UserNameGroupFail::REASON( + "IPMI length")); + } + } + if (userName.length() > systemMaxUserNameLen) + { + log("User name length limitation", + entry("SIZE=%d", userName.length())); + elog(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("Invalid user name", + entry("USER_NAME=%s", userName.c_str())); + elog(Argument::ARGUMENT_NAME("User name"), + Argument::ARGUMENT_VALUE("Invalid data")); + } +} + +void UserMgr::throwForMaxGrpUserCount( + const std::vector &groupNames) +{ + if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != + groupNames.end()) + { + if (getIpmiUsersCount() >= ipmiMaxUsers) + { + log("IPMI user limit reached"); + elog( + xyz::openbmc_project::User::Common::NoResource::REASON( + "ipmi user count reached")); + } + } + else + { + if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >= + (maxSystemUsers - ipmiMaxUsers)) + { + log("Non-ipmi User limit reached"); + elog( + 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("Invalid privilege"); + elog(Argument::ARGUMENT_NAME("Privilege"), + Argument::ARGUMENT_VALUE(priv.c_str())); + } +} + +void UserMgr::throwForInvalidGroups(const std::vector &groupNames) +{ + for (auto &group : groupNames) + { + if (std::find(groupsMgr.begin(), groupsMgr.end(), group) == + groupsMgr.end()) + { + log("Invalid Group Name listed"); + elog(Argument::ARGUMENT_NAME("GroupName"), + Argument::ARGUMENT_VALUE(group.c_str())); + } + } +} + +void UserMgr::createUser(std::string userName, + std::vector 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("Unable to create new user"); + elog(); + } + + // 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( + bus, userObj.c_str(), groupNames, priv, enabled, *this))); + + log("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("User delete failed", + entry("USER_NAME=%s", userName.c_str())); + elog(); + } + + usersList.erase(userName); + + log("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("User rename failed", + entry("USER_NAME=%s", userName.c_str())); + elog(); + } + const auto &user = usersList[userName]; + std::string priv = user.get()->userPrivilege(); + std::vector 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( + bus, newUserObj.c_str(), groupNames, priv, enabled, *this))); + return; +} + +void UserMgr::updateGroupsAndPriv(const std::string &userName, + const std::vector &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 &oldGroupNames = + usersList[userName].get()->userGroups(); + std::vector 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("Unable to modify user privilege / groups"); + elog(); + } + + log("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("Unable to modify user enabled state"); + elog(); + } + + log("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 userList; + std::vector sshUsersList; + struct passwd pw, *pwp = nullptr; + std::array buffer{}; + + phosphor::user::File passwd(passwdFileName, "r"); + if ((passwd)() == NULL) + { + log("Error opening the passwd file"); + elog(); + } + + 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 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 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 UserMgr::getUsersInGroup(const std::string &groupName) +{ + std::vector usersInGroup; + // Should be more than enough to get the pwd structure. + std::array 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("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 userNameList; + std::vector sshGrpUsersList; + UserSSHLists userSSHLists = getUserAndSshGrpList(); + userNameList = std::move(userSSHLists.first); + sshGrpUsersList = std::move(userSSHLists.second); + + if (!userNameList.empty()) + { + std::map> groupLists; + for (auto &grp : groupsMgr) + { + if (grp == grpSsh) + { + groupLists.emplace(grp, sshGrpUsersList); + } + else + { + std::vector grpUsersList = getUsersInGroup(grp); + groupLists.emplace(grp, grpUsersList); + } + } + for (auto &grp : privMgr) + { + std::vector grpUsersList = getUsersInGroup(grp); + groupLists.emplace(grp, grpUsersList); + } + + for (auto &user : userNameList) + { + std::vector userGroups; + std::string userPriv; + for (const auto &grp : groupLists) + { + std::vector 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( + 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 +#include +#include +#include +#include "users.hpp" + +namespace phosphor +{ +namespace user +{ + +using UserMgrIface = sdbusplus::xyz::openbmc_project::User::server::Manager; +using UserSSHLists = + std::pair, std::vector>; +/** @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 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 &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 privMgr = {"priv-admin", "priv-operator", + "priv-user", "priv-callback"}; + + /** @brief groups manager container */ + std::vector groupsMgr = {"web", "redfish", "ipmi", "ssh"}; + + /** @brief map container to hold users object */ + using UserName = std::string; + std::unordered_map> + 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 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 &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 &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 &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 +#include +#include +#include +#include +#include +#include +#include +#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 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 Users::userGroups(std::vector 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 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 +#include +#include +#include +#include + +namespace phosphor +{ +namespace user +{ + +namespace Base = sdbusplus::xyz::openbmc_project; +using UsersIface = + sdbusplus::server::object::object; +using DeleteIface = + sdbusplus::server::object::object; + +// 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 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 + userGroups(std::vector value) override; + + /** @brief list user groups + * + */ + std::vector 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 -- cgit v1.2.1