blob: 03003583446c3458220ac89f1f86f77d2a720114 [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);
Ratan Gupta1af12232018-11-03 00:35:38 +053079
80 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053081}
82
83/** @brief delete user method.
84 * This method deletes the user as requested
85 *
86 */
87void Users::delete_(void)
88{
89 manager.deleteUser(userName);
90}
91
92/** @brief update user privilege
93 *
94 * @param[in] value - User privilege
95 */
96std::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 Zhoufef63032022-10-25 00:07:12 +0000106void Users::setUserPrivilege(const std::string& value)
107{
108 UsersIface::userPrivilege(value);
109}
110
111void Users::setUserGroups(const std::vector<std::string>& groups)
112{
113 UsersIface::userGroups(groups);
114}
115
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530116/** @brief list user privilege
117 *
118 */
119std::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 */
128std::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 */
142std::vector<std::string> Users::userGroups(void) const
143{
144 return UsersIface::userGroups();
145}
146
147/** @brief lists user enabled state
148 *
149 */
150bool Users::userEnabled(void) const
151{
Denis Zlobine8edab52023-09-06 12:26:45 +0000152 return manager.isUserEnabled(userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530153}
154
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000155void Users::setUserEnabled(bool value)
156{
157 UsersIface::userEnabled(value);
158}
159
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530160/** @brief update user enabled state
161 *
162 * @param[in] value - bool value
163 */
164bool 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 Thomaiyarc7045192018-06-13 16:51:00 +0530174/** @brief lists user locked state for failed attempt
175 *
176 **/
177bool 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 **/
186bool 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 Reynolds3ab6cc22020-03-03 14:09:03 -0600198/** @brief indicates if the user's password is expired
199 *
200 **/
201bool Users::userPasswordExpired(void) const
202{
203 return manager.userPasswordExpired(userName);
204}
Abhilash Rajua1a754c2024-07-25 05:43:40 -0500205bool 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}
223bool Users::checkMfaStatus() const
224{
225 return (manager.enabled() != MultiFactorAuthType::None &&
226 Interfaces::bypassedProtocol() == MultiFactorAuthType::None);
227}
228std::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}
273bool 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}
301static void clearSecretFile(const std::string& path)
302{
303 if (std::filesystem::exists(path))
304 {
305 std::filesystem::remove(path);
306 }
307}
308static void clearGoogleAuthenticator(Users& thisp)
309{
310 clearSecretFile(std::format(secretKeyPath, thisp.getUserName()));
311 clearSecretFile(std::format(secretKeyTempPath, thisp.getUserName()));
312}
313static std::map<MultiFactorAuthType, std::function<void(Users&)>>
314 mfaBypassHandlers{{MultiFactorAuthType::GoogleAuthenticator,
315 clearGoogleAuthenticator},
316 {MultiFactorAuthType::None, [](Users&) {}}};
317
318MultiFactorAuthType 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
329bool Users::secretKeyIsValid() const
330{
331 std::string path = std::format(secretKeyPath, getUserName());
332 return std::filesystem::exists(path);
333}
334
335inline void googleAuthenticatorEnabled(Users& user, bool /*unused*/)
336{
337 clearGoogleAuthenticator(user);
338}
339static std::map<MultiFactorAuthType, std::function<void(Users&, bool)>>
340 mfaEnableHandlers{{MultiFactorAuthType::GoogleAuthenticator,
341 googleAuthenticatorEnabled},
342 {MultiFactorAuthType::None, [](Users&, bool) {}}};
343
344void 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}
352bool Users::secretKeyGenerationRequired() const
353{
354 return checkMfaStatus() && !secretKeyIsValid();
355}
356void Users::clearSecretKey()
357{
358 if (!checkMfaStatus())
359 {
360 throw UnsupportedRequest();
361 }
362 clearGoogleAuthenticator(*this);
363}
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600364
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530365} // namespace user
366} // namespace phosphor