Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 1 | /* |
| 2 | // Copyright (c) 2018 Intel Corporation |
| 3 | // |
| 4 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | // you may not use this file except in compliance with the License. |
| 6 | // You may obtain a copy of the License at |
| 7 | // |
| 8 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | // |
| 10 | // Unless required by applicable law or agreed to in writing, software |
| 11 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | // See the License for the specific language governing permissions and |
| 14 | // limitations under the License. |
| 15 | */ |
| 16 | |
Patrick Williams | 9638afb | 2021-02-22 17:16:24 -0600 | [diff] [blame] | 17 | #include "config.h" |
| 18 | |
| 19 | #include "users.hpp" |
| 20 | |
Abhilash Raju | a1a754c | 2024-07-25 05:43:40 -0500 | [diff] [blame] | 21 | #include "totp.hpp" |
Patrick Williams | 9638afb | 2021-02-22 17:16:24 -0600 | [diff] [blame] | 22 | #include "user_mgr.hpp" |
| 23 | |
Abhilash Raju | a1a754c | 2024-07-25 05:43:40 -0500 | [diff] [blame] | 24 | #include <pwd.h> |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 25 | #include <sys/types.h> |
| 26 | #include <sys/wait.h> |
Patrick Williams | 9638afb | 2021-02-22 17:16:24 -0600 | [diff] [blame] | 27 | #include <unistd.h> |
| 28 | |
| 29 | #include <phosphor-logging/elog-errors.hpp> |
| 30 | #include <phosphor-logging/elog.hpp> |
Jiaqing Zhao | 11ec666 | 2022-07-05 20:55:34 +0800 | [diff] [blame] | 31 | #include <phosphor-logging/lg2.hpp> |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 32 | #include <xyz/openbmc_project/Common/error.hpp> |
| 33 | #include <xyz/openbmc_project/User/Common/error.hpp> |
Patrick Williams | 9638afb | 2021-02-22 17:16:24 -0600 | [diff] [blame] | 34 | |
| 35 | #include <filesystem> |
Abhilash Raju | a1a754c | 2024-07-25 05:43:40 -0500 | [diff] [blame] | 36 | #include <fstream> |
| 37 | #include <map> |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 38 | namespace phosphor |
| 39 | { |
| 40 | namespace user |
| 41 | { |
| 42 | |
| 43 | using namespace phosphor::logging; |
| 44 | using InsufficientPermission = |
| 45 | sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; |
| 46 | using InternalFailure = |
| 47 | sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; |
| 48 | using InvalidArgument = |
| 49 | sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; |
| 50 | using NoResource = |
| 51 | sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; |
Abhilash Raju | a1a754c | 2024-07-25 05:43:40 -0500 | [diff] [blame] | 52 | using UnsupportedRequest = |
| 53 | sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest; |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 54 | |
| 55 | using Argument = xyz::openbmc_project::Common::InvalidArgument; |
Abhilash Raju | a1a754c | 2024-07-25 05:43:40 -0500 | [diff] [blame] | 56 | static constexpr auto authAppPath = "/usr/bin/google-authenticator"; |
| 57 | static constexpr auto secretKeyPath = "/home/{}/.google_authenticator"; |
| 58 | static constexpr auto secretKeyTempPath = |
| 59 | "/home/{}/.config/phosphor-user-manager/.google_authenticator.tmp"; |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 60 | |
| 61 | /** @brief Constructs UserMgr object. |
| 62 | * |
| 63 | * @param[in] bus - sdbusplus handler |
| 64 | * @param[in] path - D-Bus path |
| 65 | * @param[in] groups - users group list |
| 66 | * @param[in] priv - user privilege |
| 67 | * @param[in] enabled - user enabled state |
| 68 | * @param[in] parent - user manager - parent object |
| 69 | */ |
Patrick Williams | b3ef4e1 | 2022-07-22 19:26:55 -0500 | [diff] [blame] | 70 | Users::Users(sdbusplus::bus_t& bus, const char* path, |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 71 | std::vector<std::string> groups, std::string priv, bool enabled, |
Patrick Williams | 9638afb | 2021-02-22 17:16:24 -0600 | [diff] [blame] | 72 | UserMgr& parent) : |
Patrick Williams | 224559b | 2022-04-05 16:10:39 -0500 | [diff] [blame] | 73 | Interfaces(bus, path, Interfaces::action::defer_emit), |
P Dheeraj Srujan Kumar | b01e2fe | 2021-12-13 09:43:28 +0530 | [diff] [blame] | 74 | userName(sdbusplus::message::object_path(path).filename()), manager(parent) |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 75 | { |
| 76 | UsersIface::userPrivilege(priv, true); |
| 77 | UsersIface::userGroups(groups, true); |
| 78 | UsersIface::userEnabled(enabled, true); |
Ratan Gupta | 1af1223 | 2018-11-03 00:35:38 +0530 | [diff] [blame] | 79 | |
| 80 | this->emit_object_added(); |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 81 | } |
| 82 | |
| 83 | /** @brief delete user method. |
| 84 | * This method deletes the user as requested |
| 85 | * |
| 86 | */ |
| 87 | void Users::delete_(void) |
| 88 | { |
| 89 | manager.deleteUser(userName); |
| 90 | } |
| 91 | |
| 92 | /** @brief update user privilege |
| 93 | * |
| 94 | * @param[in] value - User privilege |
| 95 | */ |
| 96 | std::string Users::userPrivilege(std::string value) |
| 97 | { |
| 98 | if (value == UsersIface::userPrivilege()) |
| 99 | { |
| 100 | return value; |
| 101 | } |
| 102 | manager.updateGroupsAndPriv(userName, UsersIface::userGroups(), value); |
| 103 | return UsersIface::userPrivilege(value); |
| 104 | } |
| 105 | |
Nan Zhou | fef6303 | 2022-10-25 00:07:12 +0000 | [diff] [blame] | 106 | void Users::setUserPrivilege(const std::string& value) |
| 107 | { |
| 108 | UsersIface::userPrivilege(value); |
| 109 | } |
| 110 | |
| 111 | void Users::setUserGroups(const std::vector<std::string>& groups) |
| 112 | { |
| 113 | UsersIface::userGroups(groups); |
| 114 | } |
| 115 | |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 116 | /** @brief list user privilege |
| 117 | * |
| 118 | */ |
| 119 | std::string Users::userPrivilege(void) const |
| 120 | { |
| 121 | return UsersIface::userPrivilege(); |
| 122 | } |
| 123 | |
| 124 | /** @brief update user groups |
| 125 | * |
| 126 | * @param[in] value - User groups |
| 127 | */ |
| 128 | std::vector<std::string> Users::userGroups(std::vector<std::string> value) |
| 129 | { |
| 130 | if (value == UsersIface::userGroups()) |
| 131 | { |
| 132 | return value; |
| 133 | } |
| 134 | std::sort(value.begin(), value.end()); |
| 135 | manager.updateGroupsAndPriv(userName, value, UsersIface::userPrivilege()); |
| 136 | return UsersIface::userGroups(value); |
| 137 | } |
| 138 | |
| 139 | /** @brief list user groups |
| 140 | * |
| 141 | */ |
| 142 | std::vector<std::string> Users::userGroups(void) const |
| 143 | { |
| 144 | return UsersIface::userGroups(); |
| 145 | } |
| 146 | |
| 147 | /** @brief lists user enabled state |
| 148 | * |
| 149 | */ |
| 150 | bool Users::userEnabled(void) const |
| 151 | { |
Denis Zlobin | e8edab5 | 2023-09-06 12:26:45 +0000 | [diff] [blame] | 152 | return manager.isUserEnabled(userName); |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 153 | } |
| 154 | |
Nan Zhou | 6b6f2d8 | 2022-10-25 00:07:17 +0000 | [diff] [blame] | 155 | void Users::setUserEnabled(bool value) |
| 156 | { |
| 157 | UsersIface::userEnabled(value); |
| 158 | } |
| 159 | |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 160 | /** @brief update user enabled state |
| 161 | * |
| 162 | * @param[in] value - bool value |
| 163 | */ |
| 164 | bool Users::userEnabled(bool value) |
| 165 | { |
| 166 | if (value == UsersIface::userEnabled()) |
| 167 | { |
| 168 | return value; |
| 169 | } |
| 170 | manager.userEnable(userName, value); |
| 171 | return UsersIface::userEnabled(value); |
| 172 | } |
| 173 | |
Richard Marian Thomaiyar | c704519 | 2018-06-13 16:51:00 +0530 | [diff] [blame] | 174 | /** @brief lists user locked state for failed attempt |
| 175 | * |
| 176 | **/ |
| 177 | bool Users::userLockedForFailedAttempt(void) const |
| 178 | { |
| 179 | return manager.userLockedForFailedAttempt(userName); |
| 180 | } |
| 181 | |
| 182 | /** @brief unlock user locked state for failed attempt |
| 183 | * |
| 184 | * @param[in]: value - false - unlock user account, true - no action taken |
| 185 | **/ |
| 186 | bool Users::userLockedForFailedAttempt(bool value) |
| 187 | { |
| 188 | if (value != false) |
| 189 | { |
| 190 | return userLockedForFailedAttempt(); |
| 191 | } |
| 192 | else |
| 193 | { |
| 194 | return manager.userLockedForFailedAttempt(userName, value); |
| 195 | } |
| 196 | } |
| 197 | |
Joseph Reynolds | 3ab6cc2 | 2020-03-03 14:09:03 -0600 | [diff] [blame] | 198 | /** @brief indicates if the user's password is expired |
| 199 | * |
| 200 | **/ |
| 201 | bool Users::userPasswordExpired(void) const |
| 202 | { |
| 203 | return manager.userPasswordExpired(userName); |
| 204 | } |
Abhilash Raju | a1a754c | 2024-07-25 05:43:40 -0500 | [diff] [blame] | 205 | bool changeFileOwnership(const std::string& filePath, |
| 206 | const std::string& userName) |
| 207 | { |
| 208 | // Get the user ID |
| 209 | passwd* pwd = getpwnam(userName.c_str()); |
| 210 | if (pwd == nullptr) |
| 211 | { |
| 212 | lg2::error("Failed to get user ID for user:{USER}", "USER", userName); |
| 213 | return false; |
| 214 | } |
| 215 | // Change the ownership of the file |
| 216 | if (chown(filePath.c_str(), pwd->pw_uid, pwd->pw_gid) != 0) |
| 217 | { |
| 218 | lg2::error("Ownership change error {PATH}", "PATH", filePath); |
| 219 | return false; |
| 220 | } |
| 221 | return true; |
| 222 | } |
| 223 | bool Users::checkMfaStatus() const |
| 224 | { |
| 225 | return (manager.enabled() != MultiFactorAuthType::None && |
| 226 | Interfaces::bypassedProtocol() == MultiFactorAuthType::None); |
| 227 | } |
| 228 | std::string Users::createSecretKey() |
| 229 | { |
| 230 | if (!std::filesystem::exists(authAppPath)) |
| 231 | { |
| 232 | lg2::error("No authenticator app found at {PATH}", "PATH", authAppPath); |
| 233 | throw UnsupportedRequest(); |
| 234 | } |
| 235 | std::string path = std::format(secretKeyTempPath, userName); |
| 236 | if (!std::filesystem::exists(std::filesystem::path(path).parent_path())) |
| 237 | { |
| 238 | std::filesystem::create_directories( |
| 239 | std::filesystem::path(path).parent_path()); |
| 240 | } |
| 241 | /* |
| 242 | -u no-rate-limit |
| 243 | -W minimal-window |
| 244 | -Q qr-mode (NONE, ANSI, UTF8) |
| 245 | -t time-based |
| 246 | -f force file |
| 247 | -d disallow-reuse |
| 248 | -C no-confirm no confirmation required for code provisioned |
| 249 | */ |
| 250 | executeCmd(authAppPath, "-s", path.c_str(), "-u", "-W", "-Q", "NONE", "-t", |
| 251 | "-f", "-d", "-C"); |
| 252 | if (!std::filesystem::exists(path)) |
| 253 | { |
| 254 | lg2::error("Failed to create secret key for user {USER}", "USER", |
| 255 | userName); |
| 256 | throw UnsupportedRequest(); |
| 257 | } |
| 258 | std::ifstream file(path); |
| 259 | if (!file.is_open()) |
| 260 | { |
| 261 | lg2::error("Failed to open secret key file {PATH}", "PATH", path); |
| 262 | throw UnsupportedRequest(); |
| 263 | } |
| 264 | std::string secret; |
| 265 | std::getline(file, secret); |
| 266 | file.close(); |
| 267 | if (!changeFileOwnership(path, userName)) |
| 268 | { |
| 269 | throw UnsupportedRequest(); |
| 270 | } |
| 271 | return secret; |
| 272 | } |
| 273 | bool Users::verifyOTP(std::string otp) |
| 274 | { |
| 275 | if (Totp::verify(getUserName(), otp) == PAM_SUCCESS) |
| 276 | { |
| 277 | // If MFA is enabled for the user register the secret key |
| 278 | if (checkMfaStatus()) |
| 279 | { |
| 280 | try |
| 281 | { |
| 282 | std::filesystem::rename( |
| 283 | std::format(secretKeyTempPath, getUserName()), |
| 284 | std::format(secretKeyPath, getUserName())); |
| 285 | } |
| 286 | catch (const std::filesystem::filesystem_error& e) |
| 287 | { |
| 288 | lg2::error("Failed to rename file: {CODE}", "CODE", e); |
| 289 | return false; |
| 290 | } |
| 291 | } |
| 292 | else |
| 293 | { |
| 294 | std::filesystem::remove( |
| 295 | std::format(secretKeyTempPath, getUserName())); |
| 296 | } |
| 297 | return true; |
| 298 | } |
| 299 | return false; |
| 300 | } |
| 301 | static void clearSecretFile(const std::string& path) |
| 302 | { |
| 303 | if (std::filesystem::exists(path)) |
| 304 | { |
| 305 | std::filesystem::remove(path); |
| 306 | } |
| 307 | } |
| 308 | static void clearGoogleAuthenticator(Users& thisp) |
| 309 | { |
| 310 | clearSecretFile(std::format(secretKeyPath, thisp.getUserName())); |
| 311 | clearSecretFile(std::format(secretKeyTempPath, thisp.getUserName())); |
| 312 | } |
| 313 | static std::map<MultiFactorAuthType, std::function<void(Users&)>> |
| 314 | mfaBypassHandlers{{MultiFactorAuthType::GoogleAuthenticator, |
| 315 | clearGoogleAuthenticator}, |
| 316 | {MultiFactorAuthType::None, [](Users&) {}}}; |
| 317 | |
| 318 | MultiFactorAuthType Users::bypassedProtocol(MultiFactorAuthType value, |
| 319 | bool skipSignal) |
| 320 | { |
| 321 | auto iter = mfaBypassHandlers.find(value); |
| 322 | if (iter != end(mfaBypassHandlers)) |
| 323 | { |
| 324 | iter->second(*this); |
| 325 | } |
| 326 | return Interfaces::bypassedProtocol(value, skipSignal); |
| 327 | } |
| 328 | |
| 329 | bool Users::secretKeyIsValid() const |
| 330 | { |
| 331 | std::string path = std::format(secretKeyPath, getUserName()); |
| 332 | return std::filesystem::exists(path); |
| 333 | } |
| 334 | |
| 335 | inline void googleAuthenticatorEnabled(Users& user, bool /*unused*/) |
| 336 | { |
| 337 | clearGoogleAuthenticator(user); |
| 338 | } |
| 339 | static std::map<MultiFactorAuthType, std::function<void(Users&, bool)>> |
| 340 | mfaEnableHandlers{{MultiFactorAuthType::GoogleAuthenticator, |
| 341 | googleAuthenticatorEnabled}, |
| 342 | {MultiFactorAuthType::None, [](Users&, bool) {}}}; |
| 343 | |
| 344 | void Users::enableMultiFactorAuth(MultiFactorAuthType type, bool value) |
| 345 | { |
| 346 | auto iter = mfaEnableHandlers.find(type); |
| 347 | if (iter != end(mfaEnableHandlers)) |
| 348 | { |
| 349 | iter->second(*this, value); |
| 350 | } |
| 351 | } |
| 352 | bool Users::secretKeyGenerationRequired() const |
| 353 | { |
| 354 | return checkMfaStatus() && !secretKeyIsValid(); |
| 355 | } |
| 356 | void Users::clearSecretKey() |
| 357 | { |
| 358 | if (!checkMfaStatus()) |
| 359 | { |
| 360 | throw UnsupportedRequest(); |
| 361 | } |
| 362 | clearGoogleAuthenticator(*this); |
| 363 | } |
Joseph Reynolds | 3ab6cc2 | 2020-03-03 14:09:03 -0600 | [diff] [blame] | 364 | |
Richard Marian Thomaiyar | 9f630d9 | 2018-05-24 10:49:10 +0530 | [diff] [blame] | 365 | } // namespace user |
| 366 | } // namespace phosphor |