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
17 #include "config.h"
18
19 #include "users.hpp"
20
21 #include "totp.hpp"
22 #include "user_mgr.hpp"
23
24 #include <pwd.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <unistd.h>
28
29 #include <phosphor-logging/elog-errors.hpp>
30 #include <phosphor-logging/elog.hpp>
31 #include <phosphor-logging/lg2.hpp>
32 #include <xyz/openbmc_project/Common/error.hpp>
33 #include <xyz/openbmc_project/User/Common/error.hpp>
34
35 #include <filesystem>
36 #include <fstream>
37 #include <map>
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;
52 using UnsupportedRequest =
53 sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest;
54
55 using Argument = xyz::openbmc_project::Common::InvalidArgument;
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";
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 */
Users(sdbusplus::bus_t & bus,const char * path,std::vector<std::string> groups,std::string priv,bool enabled,UserMgr & parent)70 Users::Users(sdbusplus::bus_t& bus, const char* path,
71 std::vector<std::string> groups, std::string priv, bool enabled,
72 UserMgr& parent) :
73 Interfaces(bus, path, Interfaces::action::defer_emit),
74 userName(sdbusplus::message::object_path(path).filename()), manager(parent)
75 {
76 UsersIface::userPrivilege(priv, true);
77 UsersIface::userGroups(groups, true);
78 UsersIface::userEnabled(enabled, true);
79 load(manager.getSerializer());
80 this->emit_object_added();
81 }
~Users()82 Users::~Users()
83 {
84 manager.getSerializer().erase(userName);
85 }
86 /** @brief delete user method.
87 * This method deletes the user as requested
88 *
89 */
delete_(void)90 void Users::delete_(void)
91 {
92 manager.deleteUser(userName);
93 }
94
95 /** @brief update user privilege
96 *
97 * @param[in] value - User privilege
98 */
userPrivilege(std::string value)99 std::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
setUserPrivilege(const std::string & value)109 void Users::setUserPrivilege(const std::string& value)
110 {
111 UsersIface::userPrivilege(value);
112 }
113
setUserGroups(const std::vector<std::string> & groups)114 void Users::setUserGroups(const std::vector<std::string>& groups)
115 {
116 UsersIface::userGroups(groups);
117 }
118
119 /** @brief list user privilege
120 *
121 */
userPrivilege(void) const122 std::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 */
userGroups(std::vector<std::string> value)131 std::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 */
userGroups(void) const145 std::vector<std::string> Users::userGroups(void) const
146 {
147 return UsersIface::userGroups();
148 }
149
150 /** @brief lists user enabled state
151 *
152 */
userEnabled(void) const153 bool Users::userEnabled(void) const
154 {
155 return manager.isUserEnabled(userName);
156 }
157
setUserEnabled(bool value)158 void Users::setUserEnabled(bool value)
159 {
160 UsersIface::userEnabled(value);
161 }
162
163 /** @brief update user enabled state
164 *
165 * @param[in] value - bool value
166 */
userEnabled(bool value)167 bool 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
177 /** @brief lists user locked state for failed attempt
178 *
179 **/
userLockedForFailedAttempt(void) const180 bool 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 **/
userLockedForFailedAttempt(bool value)189 bool 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
201 /** @brief indicates if the user's password is expired
202 *
203 **/
userPasswordExpired(void) const204 bool Users::userPasswordExpired(void) const
205 {
206 return manager.userPasswordExpired(userName);
207 }
changeFileOwnership(const std::string & filePath,const std::string & userName)208 bool changeFileOwnership(const std::string& filePath,
209 const std::string& userName)
210 {
211 // Get the user ID
212 passwd* pwd = getpwnam(userName.c_str());
213 if (pwd == nullptr)
214 {
215 lg2::error("Failed to get user ID for user:{USER}", "USER", userName);
216 return false;
217 }
218 // Change the ownership of the file
219 if (chown(filePath.c_str(), pwd->pw_uid, pwd->pw_gid) != 0)
220 {
221 lg2::error("Ownership change error {PATH}", "PATH", filePath);
222 return false;
223 }
224 return true;
225 }
checkMfaStatus() const226 bool Users::checkMfaStatus() const
227 {
228 return (manager.enabled() != MultiFactorAuthType::None &&
229 Interfaces::bypassedProtocol() == MultiFactorAuthType::None);
230 }
createSecretKey()231 std::string Users::createSecretKey()
232 {
233 if (!std::filesystem::exists(authAppPath))
234 {
235 lg2::error("No authenticator app found at {PATH}", "PATH", authAppPath);
236 throw UnsupportedRequest();
237 }
238 std::string path = std::format(secretKeyTempPath, userName);
239 if (!std::filesystem::exists(std::filesystem::path(path).parent_path()))
240 {
241 std::filesystem::create_directories(
242 std::filesystem::path(path).parent_path());
243 }
244 /*
245 -u no-rate-limit
246 -W minimal-window
247 -Q qr-mode (NONE, ANSI, UTF8)
248 -t time-based
249 -f force file
250 -d disallow-reuse
251 -C no-confirm no confirmation required for code provisioned
252 */
253 executeCmd(authAppPath, "-s", path.c_str(), "-u", "-W", "-Q", "NONE", "-t",
254 "-f", "-d", "-C");
255 if (!std::filesystem::exists(path))
256 {
257 lg2::error("Failed to create secret key for user {USER}", "USER",
258 userName);
259 throw UnsupportedRequest();
260 }
261 std::ifstream file(path);
262 if (!file.is_open())
263 {
264 lg2::error("Failed to open secret key file {PATH}", "PATH", path);
265 throw UnsupportedRequest();
266 }
267 std::string secret;
268 std::getline(file, secret);
269 file.close();
270 if (!changeFileOwnership(path, userName))
271 {
272 throw UnsupportedRequest();
273 }
274 return secret;
275 }
verifyOTP(std::string otp)276 bool Users::verifyOTP(std::string otp)
277 {
278 if (Totp::verify(getUserName(), otp) == PAM_SUCCESS)
279 {
280 // If MFA is enabled for the user register the secret key
281 if (checkMfaStatus())
282 {
283 try
284 {
285 std::filesystem::rename(
286 std::format(secretKeyTempPath, getUserName()),
287 std::format(secretKeyPath, getUserName()));
288 }
289 catch (const std::filesystem::filesystem_error& e)
290 {
291 lg2::error("Failed to rename file: {CODE}", "CODE", e);
292 return false;
293 }
294 }
295 else
296 {
297 std::filesystem::remove(
298 std::format(secretKeyTempPath, getUserName()));
299 }
300 return true;
301 }
302 return false;
303 }
clearSecretFile(const std::string & path)304 static void clearSecretFile(const std::string& path)
305 {
306 if (std::filesystem::exists(path))
307 {
308 std::filesystem::remove(path);
309 }
310 }
clearGoogleAuthenticator(Users & thisp)311 static void clearGoogleAuthenticator(Users& thisp)
312 {
313 clearSecretFile(std::format(secretKeyPath, thisp.getUserName()));
314 clearSecretFile(std::format(secretKeyTempPath, thisp.getUserName()));
315 }
316 static std::map<MultiFactorAuthType, std::function<void(Users&)>>
317 mfaBypassHandlers{{MultiFactorAuthType::GoogleAuthenticator,
318 clearGoogleAuthenticator},
__anon90507c8b0102(Users&) 319 {MultiFactorAuthType::None, [](Users&) {}}};
320
bypassedProtocol(MultiFactorAuthType value,bool skipSignal)321 MultiFactorAuthType Users::bypassedProtocol(MultiFactorAuthType value,
322 bool skipSignal)
323 {
324 auto iter = mfaBypassHandlers.find(value);
325 if (iter != end(mfaBypassHandlers))
326 {
327 iter->second(*this);
328 }
329 std::string path = std::format("{}/bypassedprotocol", getUserName());
330 manager.getSerializer().serialize(
331 path, MultiFactorAuthConfiguration::convertTypeToString(value));
332 manager.getSerializer().store();
333 return Interfaces::bypassedProtocol(value, skipSignal);
334 }
335
secretKeyIsValid() const336 bool Users::secretKeyIsValid() const
337 {
338 std::string path = std::format(secretKeyPath, getUserName());
339 return std::filesystem::exists(path);
340 }
341
googleAuthenticatorEnabled(Users & user,bool)342 inline void googleAuthenticatorEnabled(Users& user, bool /*unused*/)
343 {
344 clearGoogleAuthenticator(user);
345 }
346 static std::map<MultiFactorAuthType, std::function<void(Users&, bool)>>
347 mfaEnableHandlers{{MultiFactorAuthType::GoogleAuthenticator,
348 googleAuthenticatorEnabled},
__anon90507c8b0202(Users&, bool) 349 {MultiFactorAuthType::None, [](Users&, bool) {}}};
350
enableMultiFactorAuth(MultiFactorAuthType type,bool value)351 void Users::enableMultiFactorAuth(MultiFactorAuthType type, bool value)
352 {
353 auto iter = mfaEnableHandlers.find(type);
354 if (iter != end(mfaEnableHandlers))
355 {
356 iter->second(*this, value);
357 }
358 }
secretKeyGenerationRequired() const359 bool Users::secretKeyGenerationRequired() const
360 {
361 return checkMfaStatus() && !secretKeyIsValid();
362 }
clearSecretKey()363 void Users::clearSecretKey()
364 {
365 if (!checkMfaStatus())
366 {
367 throw UnsupportedRequest();
368 }
369 clearGoogleAuthenticator(*this);
370 }
371
load(JsonSerializer & ts)372 void Users::load(JsonSerializer& ts)
373 {
374 std::optional<std::string> protocol;
375 std::string path = std::format("{}/bypassedprotocol", userName);
376 ts.deserialize(path, protocol);
377 if (protocol)
378 {
379 MultiFactorAuthType type =
380 MultiFactorAuthConfiguration::convertTypeFromString(*protocol);
381 bypassedProtocol(type, true);
382 return;
383 }
384 bypassedProtocol(MultiFactorAuthType::None, true);
385 ts.serialize(path, MultiFactorAuthConfiguration::convertTypeToString(
386 MultiFactorAuthType::None));
387 }
388
389 } // namespace user
390 } // namespace phosphor
391