blob: 90080086cbca03e11d24ff667e137c5feacad015 [file] [log] [blame]
/*
// 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