/* // 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 "config.h" #include "users.hpp" #include "totp.hpp" #include "user_mgr.hpp" #include #include #include #include #include #include #include #include #include #include #include #include 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 UnsupportedRequest = sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest; using Argument = xyz::openbmc_project::Common::InvalidArgument; static constexpr auto authAppPath = "/usr/bin/google-authenticator"; static constexpr auto secretKeyPath = "/home/{}/.google_authenticator"; static constexpr auto secretKeyTempPath = "/home/{}/.config/phosphor-user-manager/.google_authenticator.tmp"; /** @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_t& bus, const char* path, std::vector groups, std::string priv, bool enabled, UserMgr& parent) : Interfaces(bus, path, Interfaces::action::defer_emit), userName(sdbusplus::message::object_path(path).filename()), manager(parent) { UsersIface::userPrivilege(priv, true); UsersIface::userGroups(groups, true); UsersIface::userEnabled(enabled, true); load(manager.getSerializer()); this->emit_object_added(); } Users::~Users() { manager.getSerializer().erase(userName); } /** @brief delete user method. * This method deletes the user as requested * */ 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); } void Users::setUserPrivilege(const std::string& value) { UsersIface::userPrivilege(value); } void Users::setUserGroups(const std::vector& groups) { UsersIface::userGroups(groups); } /** @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 manager.isUserEnabled(userName); } void Users::setUserEnabled(bool value) { UsersIface::userEnabled(value); } /** @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); } /** @brief lists user locked state for failed attempt * **/ bool Users::userLockedForFailedAttempt(void) const { return manager.userLockedForFailedAttempt(userName); } /** @brief unlock user locked state for failed attempt * * @param[in]: value - false - unlock user account, true - no action taken **/ bool Users::userLockedForFailedAttempt(bool value) { if (value != false) { return userLockedForFailedAttempt(); } else { return manager.userLockedForFailedAttempt(userName, value); } } /** @brief indicates if the user's password is expired * **/ bool Users::userPasswordExpired(void) const { return manager.userPasswordExpired(userName); } bool changeFileOwnership(const std::string& userName) { // Get the user ID passwd* pwd = getpwnam(userName.c_str()); if (pwd == nullptr) { lg2::error("Failed to get user ID for user:{USER}", "USER", userName); return false; } // Change the ownership of the file // Change ownership recursively for the user's home directory std::string homeDir = std::format("/home/{}/", userName); for (const auto& entry : std::filesystem::recursive_directory_iterator(homeDir)) { if (chown(entry.path().c_str(), pwd->pw_uid, pwd->pw_gid) != 0) { lg2::error("Ownership change error {PATH}", "PATH", entry.path().string()); return false; } } return true; } bool Users::checkMfaStatus() const { return (manager.enabled() != MultiFactorAuthType::None && Interfaces::bypassedProtocol() == MultiFactorAuthType::None); } std::string Users::createSecretKey() { if (!std::filesystem::exists(authAppPath)) { lg2::error("No authenticator app found at {PATH}", "PATH", authAppPath); throw UnsupportedRequest(); } std::string path = std::format(secretKeyTempPath, userName); if (!std::filesystem::exists(std::filesystem::path(path).parent_path())) { std::filesystem::create_directories( std::filesystem::path(path).parent_path()); } /* -u no-rate-limit -W minimal-window -Q qr-mode (NONE, ANSI, UTF8) -t time-based -f force file -d disallow-reuse -C no-confirm no confirmation required for code provisioned */ executeCmd(authAppPath, "-s", path.c_str(), "-u", "-W", "-Q", "NONE", "-t", "-f", "-d", "-C"); if (!std::filesystem::exists(path)) { lg2::error("Failed to create secret key for user {USER}", "USER", userName); throw UnsupportedRequest(); } std::ifstream file(path); if (!file.is_open()) { lg2::error("Failed to open secret key file {PATH}", "PATH", path); throw UnsupportedRequest(); } std::string secret; std::getline(file, secret); file.close(); if (!changeFileOwnership(userName)) { throw UnsupportedRequest(); } return secret; } bool Users::verifyOTP(std::string otp) { if (Totp::verify(getUserName(), otp) == PAM_SUCCESS) { // If MFA is enabled for the user register the secret key if (checkMfaStatus()) { try { std::filesystem::rename( std::format(secretKeyTempPath, getUserName()), std::format(secretKeyPath, getUserName())); } catch (const std::filesystem::filesystem_error& e) { lg2::error("Failed to rename file: {CODE}", "CODE", e); return false; } } else { std::filesystem::remove( std::format(secretKeyTempPath, getUserName())); } return true; } return false; } static void clearSecretFile(const std::string& path) { if (std::filesystem::exists(path)) { std::filesystem::remove(path); } } static void clearGoogleAuthenticator(Users& thisp) { clearSecretFile(std::format(secretKeyPath, thisp.getUserName())); clearSecretFile(std::format(secretKeyTempPath, thisp.getUserName())); } static std::map> mfaBypassHandlers{{MultiFactorAuthType::GoogleAuthenticator, clearGoogleAuthenticator}, {MultiFactorAuthType::None, [](Users&) {}}}; MultiFactorAuthType Users::bypassedProtocol(MultiFactorAuthType value, bool skipSignal) { auto iter = mfaBypassHandlers.find(value); if (iter != end(mfaBypassHandlers)) { iter->second(*this); } std::string path = std::format("{}/bypassedprotocol", getUserName()); manager.getSerializer().serialize( path, MultiFactorAuthConfiguration::convertTypeToString(value)); manager.getSerializer().store(); return Interfaces::bypassedProtocol(value, skipSignal); } bool Users::secretKeyIsValid() const { std::string path = std::format(secretKeyPath, getUserName()); return std::filesystem::exists(path); } inline void googleAuthenticatorEnabled(Users& user, bool /*unused*/) { clearGoogleAuthenticator(user); } static std::map> mfaEnableHandlers{{MultiFactorAuthType::GoogleAuthenticator, googleAuthenticatorEnabled}, {MultiFactorAuthType::None, [](Users&, bool) {}}}; void Users::enableMultiFactorAuth(MultiFactorAuthType type, bool value) { auto iter = mfaEnableHandlers.find(type); if (iter != end(mfaEnableHandlers)) { iter->second(*this, value); } } bool Users::secretKeyGenerationRequired() const { return checkMfaStatus() && !secretKeyIsValid(); } void Users::clearSecretKey() { if (!checkMfaStatus()) { throw UnsupportedRequest(); } clearGoogleAuthenticator(*this); } void Users::load(JsonSerializer& ts) { std::optional protocol; std::string path = std::format("{}/bypassedprotocol", userName); ts.deserialize(path, protocol); if (protocol) { MultiFactorAuthType type = MultiFactorAuthConfiguration::convertTypeFromString(*protocol); bypassedProtocol(type, true); return; } bypassedProtocol(MultiFactorAuthType::None, true); ts.serialize(path, MultiFactorAuthConfiguration::convertTypeToString( MultiFactorAuthType::None)); } } // namespace user } // namespace phosphor