blob: 29aaf27865500a07e133ef93d4a0a59dcf1b20a2 [file] [log] [blame]
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301/*
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 Williams9638afb2021-02-22 17:16:24 -060017#include "config.h"
18
19#include "users.hpp"
20
Abhilash Rajua1a754c2024-07-25 05:43:40 -050021#include "totp.hpp"
Patrick Williams9638afb2021-02-22 17:16:24 -060022#include "user_mgr.hpp"
23
Abhilash Rajua1a754c2024-07-25 05:43:40 -050024#include <pwd.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053025#include <sys/types.h>
26#include <sys/wait.h>
Patrick Williams9638afb2021-02-22 17:16:24 -060027#include <unistd.h>
28
29#include <phosphor-logging/elog-errors.hpp>
30#include <phosphor-logging/elog.hpp>
Jiaqing Zhao11ec6662022-07-05 20:55:34 +080031#include <phosphor-logging/lg2.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053032#include <xyz/openbmc_project/Common/error.hpp>
33#include <xyz/openbmc_project/User/Common/error.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060034
35#include <filesystem>
Abhilash Rajua1a754c2024-07-25 05:43:40 -050036#include <fstream>
37#include <map>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053038namespace phosphor
39{
40namespace user
41{
42
43using namespace phosphor::logging;
44using InsufficientPermission =
45 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
46using InternalFailure =
47 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
48using InvalidArgument =
49 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
50using NoResource =
51 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
Abhilash Rajua1a754c2024-07-25 05:43:40 -050052using UnsupportedRequest =
53 sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053054
55using Argument = xyz::openbmc_project::Common::InvalidArgument;
Abhilash Rajua1a754c2024-07-25 05:43:40 -050056static constexpr auto authAppPath = "/usr/bin/google-authenticator";
57static constexpr auto secretKeyPath = "/home/{}/.google_authenticator";
58static constexpr auto secretKeyTempPath =
59 "/home/{}/.config/phosphor-user-manager/.google_authenticator.tmp";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053060
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 Williamsb3ef4e12022-07-22 19:26:55 -050070Users::Users(sdbusplus::bus_t& bus, const char* path,
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053071 std::vector<std::string> groups, std::string priv, bool enabled,
Patrick Williams9638afb2021-02-22 17:16:24 -060072 UserMgr& parent) :
Patrick Williams224559b2022-04-05 16:10:39 -050073 Interfaces(bus, path, Interfaces::action::defer_emit),
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +053074 userName(sdbusplus::message::object_path(path).filename()), manager(parent)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053075{
76 UsersIface::userPrivilege(priv, true);
77 UsersIface::userGroups(groups, true);
78 UsersIface::userEnabled(enabled, true);
Abhilash Raju93804eb2024-10-01 00:24:43 -050079 load(manager.getSerializer());
Ratan Gupta1af12232018-11-03 00:35:38 +053080 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053081}
Abhilash Raju93804eb2024-10-01 00:24:43 -050082Users::~Users()
83{
84 manager.getSerializer().erase(userName);
85}
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053086/** @brief delete user method.
87 * This method deletes the user as requested
88 *
89 */
90void Users::delete_(void)
91{
92 manager.deleteUser(userName);
93}
94
95/** @brief update user privilege
96 *
97 * @param[in] value - User privilege
98 */
99std::string Users::userPrivilege(std::string value)
100{
101 if (value == UsersIface::userPrivilege())
102 {
103 return value;
104 }
105 manager.updateGroupsAndPriv(userName, UsersIface::userGroups(), value);
106 return UsersIface::userPrivilege(value);
107}
108
Nan Zhoufef63032022-10-25 00:07:12 +0000109void Users::setUserPrivilege(const std::string& value)
110{
111 UsersIface::userPrivilege(value);
112}
113
114void Users::setUserGroups(const std::vector<std::string>& groups)
115{
116 UsersIface::userGroups(groups);
117}
118
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530119/** @brief list user privilege
120 *
121 */
122std::string Users::userPrivilege(void) const
123{
124 return UsersIface::userPrivilege();
125}
126
127/** @brief update user groups
128 *
129 * @param[in] value - User groups
130 */
131std::vector<std::string> Users::userGroups(std::vector<std::string> value)
132{
133 if (value == UsersIface::userGroups())
134 {
135 return value;
136 }
137 std::sort(value.begin(), value.end());
138 manager.updateGroupsAndPriv(userName, value, UsersIface::userPrivilege());
139 return UsersIface::userGroups(value);
140}
141
142/** @brief list user groups
143 *
144 */
145std::vector<std::string> Users::userGroups(void) const
146{
147 return UsersIface::userGroups();
148}
149
150/** @brief lists user enabled state
151 *
152 */
153bool Users::userEnabled(void) const
154{
Denis Zlobine8edab52023-09-06 12:26:45 +0000155 return manager.isUserEnabled(userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530156}
157
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000158void Users::setUserEnabled(bool value)
159{
160 UsersIface::userEnabled(value);
161}
162
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530163/** @brief update user enabled state
164 *
165 * @param[in] value - bool value
166 */
167bool Users::userEnabled(bool value)
168{
169 if (value == UsersIface::userEnabled())
170 {
171 return value;
172 }
173 manager.userEnable(userName, value);
174 return UsersIface::userEnabled(value);
175}
176
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530177/** @brief lists user locked state for failed attempt
178 *
179 **/
180bool Users::userLockedForFailedAttempt(void) const
181{
182 return manager.userLockedForFailedAttempt(userName);
183}
184
185/** @brief unlock user locked state for failed attempt
186 *
187 * @param[in]: value - false - unlock user account, true - no action taken
188 **/
189bool Users::userLockedForFailedAttempt(bool value)
190{
191 if (value != false)
192 {
193 return userLockedForFailedAttempt();
194 }
195 else
196 {
197 return manager.userLockedForFailedAttempt(userName, value);
198 }
199}
200
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600201/** @brief indicates if the user's password is expired
202 *
203 **/
204bool Users::userPasswordExpired(void) const
205{
206 return manager.userPasswordExpired(userName);
207}
Abhilash Raju3ddb95a2025-06-13 06:17:15 +0000208bool changeFileOwnership(const std::string& userName)
Abhilash Rajua1a754c2024-07-25 05:43:40 -0500209{
210 // Get the user ID
211 passwd* pwd = getpwnam(userName.c_str());
212 if (pwd == nullptr)
213 {
214 lg2::error("Failed to get user ID for user:{USER}", "USER", userName);
215 return false;
216 }
217 // Change the ownership of the file
Abhilash Raju3ddb95a2025-06-13 06:17:15 +0000218 // Change ownership recursively for the user's home directory
219 std::string homeDir = std::format("/home/{}/", userName);
220 for (const auto& entry :
221 std::filesystem::recursive_directory_iterator(homeDir))
Abhilash Rajua1a754c2024-07-25 05:43:40 -0500222 {
Abhilash Raju3ddb95a2025-06-13 06:17:15 +0000223 if (chown(entry.path().c_str(), pwd->pw_uid, pwd->pw_gid) != 0)
224 {
225 lg2::error("Ownership change error {PATH}", "PATH",
226 entry.path().string());
227 return false;
228 }
Abhilash Rajua1a754c2024-07-25 05:43:40 -0500229 }
230 return true;
231}
232bool Users::checkMfaStatus() const
233{
234 return (manager.enabled() != MultiFactorAuthType::None &&
235 Interfaces::bypassedProtocol() == MultiFactorAuthType::None);
236}
237std::string Users::createSecretKey()
238{
239 if (!std::filesystem::exists(authAppPath))
240 {
241 lg2::error("No authenticator app found at {PATH}", "PATH", authAppPath);
242 throw UnsupportedRequest();
243 }
244 std::string path = std::format(secretKeyTempPath, userName);
245 if (!std::filesystem::exists(std::filesystem::path(path).parent_path()))
246 {
247 std::filesystem::create_directories(
248 std::filesystem::path(path).parent_path());
249 }
250 /*
251 -u no-rate-limit
252 -W minimal-window
253 -Q qr-mode (NONE, ANSI, UTF8)
254 -t time-based
255 -f force file
256 -d disallow-reuse
257 -C no-confirm no confirmation required for code provisioned
258 */
259 executeCmd(authAppPath, "-s", path.c_str(), "-u", "-W", "-Q", "NONE", "-t",
260 "-f", "-d", "-C");
261 if (!std::filesystem::exists(path))
262 {
263 lg2::error("Failed to create secret key for user {USER}", "USER",
264 userName);
265 throw UnsupportedRequest();
266 }
267 std::ifstream file(path);
268 if (!file.is_open())
269 {
270 lg2::error("Failed to open secret key file {PATH}", "PATH", path);
271 throw UnsupportedRequest();
272 }
273 std::string secret;
274 std::getline(file, secret);
275 file.close();
Abhilash Raju3ddb95a2025-06-13 06:17:15 +0000276 if (!changeFileOwnership(userName))
Abhilash Rajua1a754c2024-07-25 05:43:40 -0500277 {
278 throw UnsupportedRequest();
279 }
280 return secret;
281}
282bool Users::verifyOTP(std::string otp)
283{
284 if (Totp::verify(getUserName(), otp) == PAM_SUCCESS)
285 {
286 // If MFA is enabled for the user register the secret key
287 if (checkMfaStatus())
288 {
289 try
290 {
291 std::filesystem::rename(
292 std::format(secretKeyTempPath, getUserName()),
293 std::format(secretKeyPath, getUserName()));
294 }
295 catch (const std::filesystem::filesystem_error& e)
296 {
297 lg2::error("Failed to rename file: {CODE}", "CODE", e);
298 return false;
299 }
300 }
301 else
302 {
303 std::filesystem::remove(
304 std::format(secretKeyTempPath, getUserName()));
305 }
306 return true;
307 }
308 return false;
309}
310static void clearSecretFile(const std::string& path)
311{
312 if (std::filesystem::exists(path))
313 {
314 std::filesystem::remove(path);
315 }
316}
317static void clearGoogleAuthenticator(Users& thisp)
318{
319 clearSecretFile(std::format(secretKeyPath, thisp.getUserName()));
320 clearSecretFile(std::format(secretKeyTempPath, thisp.getUserName()));
321}
322static std::map<MultiFactorAuthType, std::function<void(Users&)>>
323 mfaBypassHandlers{{MultiFactorAuthType::GoogleAuthenticator,
324 clearGoogleAuthenticator},
325 {MultiFactorAuthType::None, [](Users&) {}}};
326
327MultiFactorAuthType Users::bypassedProtocol(MultiFactorAuthType value,
328 bool skipSignal)
329{
330 auto iter = mfaBypassHandlers.find(value);
331 if (iter != end(mfaBypassHandlers))
332 {
333 iter->second(*this);
334 }
Abhilash Raju93804eb2024-10-01 00:24:43 -0500335 std::string path = std::format("{}/bypassedprotocol", getUserName());
336 manager.getSerializer().serialize(
337 path, MultiFactorAuthConfiguration::convertTypeToString(value));
338 manager.getSerializer().store();
Abhilash Rajua1a754c2024-07-25 05:43:40 -0500339 return Interfaces::bypassedProtocol(value, skipSignal);
340}
341
342bool Users::secretKeyIsValid() const
343{
344 std::string path = std::format(secretKeyPath, getUserName());
345 return std::filesystem::exists(path);
346}
347
348inline void googleAuthenticatorEnabled(Users& user, bool /*unused*/)
349{
350 clearGoogleAuthenticator(user);
351}
352static std::map<MultiFactorAuthType, std::function<void(Users&, bool)>>
353 mfaEnableHandlers{{MultiFactorAuthType::GoogleAuthenticator,
354 googleAuthenticatorEnabled},
355 {MultiFactorAuthType::None, [](Users&, bool) {}}};
356
357void Users::enableMultiFactorAuth(MultiFactorAuthType type, bool value)
358{
359 auto iter = mfaEnableHandlers.find(type);
360 if (iter != end(mfaEnableHandlers))
361 {
362 iter->second(*this, value);
363 }
364}
365bool Users::secretKeyGenerationRequired() const
366{
367 return checkMfaStatus() && !secretKeyIsValid();
368}
369void Users::clearSecretKey()
370{
371 if (!checkMfaStatus())
372 {
373 throw UnsupportedRequest();
374 }
375 clearGoogleAuthenticator(*this);
376}
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600377
Abhilash Raju93804eb2024-10-01 00:24:43 -0500378void Users::load(JsonSerializer& ts)
379{
380 std::optional<std::string> protocol;
381 std::string path = std::format("{}/bypassedprotocol", userName);
382 ts.deserialize(path, protocol);
383 if (protocol)
384 {
385 MultiFactorAuthType type =
386 MultiFactorAuthConfiguration::convertTypeFromString(*protocol);
387 bypassedProtocol(type, true);
388 return;
389 }
390 bypassedProtocol(MultiFactorAuthType::None, true);
391 ts.serialize(path, MultiFactorAuthConfiguration::convertTypeToString(
392 MultiFactorAuthType::None));
393}
394
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395} // namespace user
396} // namespace phosphor