xref: /openbmc/phosphor-user-manager/users.cpp (revision 2a137f4de7d678cdc87f5d6b62c85fa7b1d95ec1)
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 Users 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] passwordExpiration - user password expiration Epoch time
69  *  @param[in] parent - user manager - parent object
70  */
71 Users::Users(sdbusplus::bus_t& bus, const char* path,
72              std::vector<std::string> groups, std::string priv, bool enabled,
73              std::optional<uint64_t> passwordExpiration, UserMgr& parent) :
74     Interfaces(bus, path, Interfaces::action::defer_emit),
75     userName(sdbusplus::message::object_path(path).filename()), manager(parent)
76 {
77     UsersIface::userPrivilege(priv, true);
78     UsersIface::userGroups(groups, true);
79     UsersIface::userEnabled(enabled, true);
80     if (passwordExpiration)
81         UsersIface::passwordExpiration(*passwordExpiration);
82     load(manager.getSerializer());
83     this->emit_object_added();
84 }
85 Users::~Users()
86 {
87     manager.getSerializer().erase(userName);
88 }
89 /** @brief delete user method.
90  *  This method deletes the user as requested
91  *
92  */
93 void Users::delete_(void)
94 {
95     manager.deleteUser(userName);
96 }
97 
98 /** @brief update user privilege
99  *
100  *  @param[in] value - User privilege
101  */
102 std::string Users::userPrivilege(std::string value)
103 {
104     if (value == UsersIface::userPrivilege())
105     {
106         return value;
107     }
108     manager.updateGroupsAndPriv(userName, UsersIface::userGroups(), value);
109     return UsersIface::userPrivilege(value);
110 }
111 
112 void Users::setUserPrivilege(const std::string& value)
113 {
114     UsersIface::userPrivilege(value);
115 }
116 
117 void Users::setUserGroups(const std::vector<std::string>& groups)
118 {
119     UsersIface::userGroups(groups);
120 }
121 
122 /** @brief list user privilege
123  *
124  */
125 std::string Users::userPrivilege(void) const
126 {
127     return UsersIface::userPrivilege();
128 }
129 
130 /** @brief update user groups
131  *
132  *  @param[in] value - User groups
133  */
134 std::vector<std::string> Users::userGroups(std::vector<std::string> value)
135 {
136     if (value == UsersIface::userGroups())
137     {
138         return value;
139     }
140     std::sort(value.begin(), value.end());
141     manager.updateGroupsAndPriv(userName, value, UsersIface::userPrivilege());
142     return UsersIface::userGroups(value);
143 }
144 
145 /** @brief list user groups
146  *
147  */
148 std::vector<std::string> Users::userGroups(void) const
149 {
150     return UsersIface::userGroups();
151 }
152 
153 /** @brief lists user enabled state
154  *
155  */
156 bool Users::userEnabled(void) const
157 {
158     return manager.isUserEnabled(userName);
159 }
160 
161 void Users::setUserEnabled(bool value)
162 {
163     UsersIface::userEnabled(value);
164 }
165 
166 /** @brief update user enabled state
167  *
168  *  @param[in] value - bool value
169  */
170 bool Users::userEnabled(bool value)
171 {
172     if (value == UsersIface::userEnabled())
173     {
174         return value;
175     }
176     manager.userEnable(userName, value);
177     return UsersIface::userEnabled(value);
178 }
179 
180 /** @brief lists user locked state for failed attempt
181  *
182  **/
183 bool Users::userLockedForFailedAttempt(void) const
184 {
185     return manager.userLockedForFailedAttempt(userName);
186 }
187 
188 /** @brief unlock user locked state for failed attempt
189  *
190  * @param[in]: value - false - unlock user account, true - no action taken
191  **/
192 bool Users::userLockedForFailedAttempt(bool value)
193 {
194     if (value != false)
195     {
196         return userLockedForFailedAttempt();
197     }
198     else
199     {
200         return manager.userLockedForFailedAttempt(userName, value);
201     }
202 }
203 
204 /** @brief indicates if the user's password is expired
205  *
206  **/
207 bool Users::userPasswordExpired(void) const
208 {
209     return manager.userPasswordExpired(userName);
210 }
211 bool changeFileOwnership(const std::string& userName)
212 {
213     // Get the user ID
214     passwd* pwd = getpwnam(userName.c_str());
215     if (pwd == nullptr)
216     {
217         lg2::error("Failed to get user ID for user:{USER}", "USER", userName);
218         return false;
219     }
220     // Change the ownership of the file
221     // Change ownership recursively for the user's home directory
222     std::string homeDir = std::format("/home/{}/", userName);
223     for (const auto& entry :
224          std::filesystem::recursive_directory_iterator(homeDir))
225     {
226         if (chown(entry.path().c_str(), pwd->pw_uid, pwd->pw_gid) != 0)
227         {
228             lg2::error("Ownership change error {PATH}", "PATH",
229                        entry.path().string());
230             return false;
231         }
232     }
233     return true;
234 }
235 bool Users::checkMfaStatus() const
236 {
237     return (manager.enabled() != MultiFactorAuthType::None &&
238             Interfaces::bypassedProtocol() == MultiFactorAuthType::None);
239 }
240 std::string Users::createSecretKey()
241 {
242     if (!std::filesystem::exists(authAppPath))
243     {
244         lg2::error("No authenticator app found at {PATH}", "PATH", authAppPath);
245         throw UnsupportedRequest();
246     }
247     std::string path = std::format(secretKeyTempPath, userName);
248     if (!std::filesystem::exists(std::filesystem::path(path).parent_path()))
249     {
250         std::filesystem::create_directories(
251             std::filesystem::path(path).parent_path());
252     }
253     /*
254     -u no-rate-limit
255     -W minimal-window
256     -Q qr-mode (NONE, ANSI, UTF8)
257     -t time-based
258     -f force file
259     -d disallow-reuse
260     -C no-confirm no confirmation required for code provisioned
261     */
262     executeCmd(authAppPath, "-s", path.c_str(), "-u", "-W", "-Q", "NONE", "-t",
263                "-f", "-d", "-C");
264     if (!std::filesystem::exists(path))
265     {
266         lg2::error("Failed to create secret key for user {USER}", "USER",
267                    userName);
268         throw UnsupportedRequest();
269     }
270     std::ifstream file(path);
271     if (!file.is_open())
272     {
273         lg2::error("Failed to open secret key file {PATH}", "PATH", path);
274         throw UnsupportedRequest();
275     }
276     std::string secret;
277     std::getline(file, secret);
278     file.close();
279     if (!changeFileOwnership(userName))
280     {
281         throw UnsupportedRequest();
282     }
283     return secret;
284 }
285 bool Users::verifyOTP(std::string otp)
286 {
287     if (Totp::verify(getUserName(), otp) == PAM_SUCCESS)
288     {
289         // If MFA is enabled for the user register the secret key
290         if (checkMfaStatus())
291         {
292             try
293             {
294                 std::filesystem::rename(
295                     std::format(secretKeyTempPath, getUserName()),
296                     std::format(secretKeyPath, getUserName()));
297             }
298             catch (const std::filesystem::filesystem_error& e)
299             {
300                 lg2::error("Failed to rename file: {CODE}", "CODE", e);
301                 return false;
302             }
303         }
304         else
305         {
306             std::filesystem::remove(
307                 std::format(secretKeyTempPath, getUserName()));
308         }
309         return true;
310     }
311     return false;
312 }
313 static void clearSecretFile(const std::string& path)
314 {
315     if (std::filesystem::exists(path))
316     {
317         std::filesystem::remove(path);
318     }
319 }
320 static void clearGoogleAuthenticator(Users& thisp)
321 {
322     clearSecretFile(std::format(secretKeyPath, thisp.getUserName()));
323     clearSecretFile(std::format(secretKeyTempPath, thisp.getUserName()));
324 }
325 static std::map<MultiFactorAuthType, std::function<void(Users&)>>
326     mfaBypassHandlers{{MultiFactorAuthType::GoogleAuthenticator,
327                        clearGoogleAuthenticator},
328                       {MultiFactorAuthType::None, [](Users&) {}}};
329 
330 MultiFactorAuthType Users::bypassedProtocol(MultiFactorAuthType value,
331                                             bool skipSignal)
332 {
333     auto iter = mfaBypassHandlers.find(value);
334     if (iter != end(mfaBypassHandlers))
335     {
336         iter->second(*this);
337     }
338     std::string path = std::format("{}/bypassedprotocol", getUserName());
339     manager.getSerializer().serialize(
340         path, MultiFactorAuthConfiguration::convertTypeToString(value));
341     manager.getSerializer().store();
342     return Interfaces::bypassedProtocol(value, skipSignal);
343 }
344 
345 bool Users::secretKeyIsValid() const
346 {
347     std::string path = std::format(secretKeyPath, getUserName());
348     return std::filesystem::exists(path);
349 }
350 
351 inline void googleAuthenticatorEnabled(Users& user, bool /*unused*/)
352 {
353     clearGoogleAuthenticator(user);
354 }
355 static std::map<MultiFactorAuthType, std::function<void(Users&, bool)>>
356     mfaEnableHandlers{{MultiFactorAuthType::GoogleAuthenticator,
357                        googleAuthenticatorEnabled},
358                       {MultiFactorAuthType::None, [](Users&, bool) {}}};
359 
360 void Users::enableMultiFactorAuth(MultiFactorAuthType type, bool value)
361 {
362     auto iter = mfaEnableHandlers.find(type);
363     if (iter != end(mfaEnableHandlers))
364     {
365         iter->second(*this, value);
366     }
367 }
368 bool Users::secretKeyGenerationRequired() const
369 {
370     return checkMfaStatus() && !secretKeyIsValid();
371 }
372 void Users::clearSecretKey()
373 {
374     if (!checkMfaStatus())
375     {
376         throw UnsupportedRequest();
377     }
378     clearGoogleAuthenticator(*this);
379 }
380 
381 void Users::load(JsonSerializer& ts)
382 {
383     std::optional<std::string> protocol;
384     std::string path = std::format("{}/bypassedprotocol", userName);
385     ts.deserialize(path, protocol);
386     if (protocol)
387     {
388         MultiFactorAuthType type =
389             MultiFactorAuthConfiguration::convertTypeFromString(*protocol);
390         bypassedProtocol(type, true);
391         return;
392     }
393     bypassedProtocol(MultiFactorAuthType::None, true);
394     ts.serialize(path, MultiFactorAuthConfiguration::convertTypeToString(
395                            MultiFactorAuthType::None));
396 }
397 
398 /** @brief user password expiration Epoch time
399  *
400  **/
401 uint64_t Users::passwordExpiration() const
402 {
403     return UsersIface::passwordExpiration();
404 }
405 
406 /** @brief update user password expiration Epoch time
407  *
408  **/
409 uint64_t Users::passwordExpiration(uint64_t value)
410 {
411     manager.setPasswordExpiration(userName, value);
412 
413     return UsersIface::passwordExpiration(value);
414 }
415 
416 } // namespace user
417 } // namespace phosphor
418