| /* |
| // 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 <pwd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/elog.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| #include <xyz/openbmc_project/User/Common/error.hpp> |
| |
| #include <filesystem> |
| #include <fstream> |
| #include <map> |
| 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<std::string> 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<std::string>& 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<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 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& filePath, |
| 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 |
| if (chown(filePath.c_str(), pwd->pw_uid, pwd->pw_gid) != 0) |
| { |
| lg2::error("Ownership change error {PATH}", "PATH", filePath); |
| 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(path, 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<MultiFactorAuthType, std::function<void(Users&)>> |
| 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<MultiFactorAuthType, std::function<void(Users&, bool)>> |
| 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<std::string> 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 |