xref: /openbmc/bmcweb/features/redfish/lib/account_service.hpp (revision 19fb6e719849aea2f0cafdee64cd7da8a7a2b67b)
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 #pragma once
17 #include "node.hpp"
18 
19 #include <dbus_utility.hpp>
20 #include <error_messages.hpp>
21 #include <openbmc_dbus_rest.hpp>
22 #include <utils/json_utils.hpp>
23 #include <variant>
24 
25 namespace redfish
26 {
27 
28 using ManagedObjectType = std::vector<std::pair<
29     sdbusplus::message::object_path,
30     boost::container::flat_map<
31         std::string, boost::container::flat_map<
32                          std::string, std::variant<bool, std::string>>>>>;
33 
34 inline std::string getPrivilegeFromRoleId(boost::beast::string_view role)
35 {
36     if (role == "priv-admin")
37     {
38         return "Administrator";
39     }
40     else if (role == "priv-callback")
41     {
42         return "Callback";
43     }
44     else if (role == "priv-user")
45     {
46         return "User";
47     }
48     else if (role == "priv-operator")
49     {
50         return "Operator";
51     }
52     return "";
53 }
54 inline std::string getRoleIdFromPrivilege(boost::beast::string_view role)
55 {
56     if (role == "Administrator")
57     {
58         return "priv-admin";
59     }
60     else if (role == "Callback")
61     {
62         return "priv-callback";
63     }
64     else if (role == "User")
65     {
66         return "priv-user";
67     }
68     else if (role == "Operator")
69     {
70         return "priv-operator";
71     }
72     return "";
73 }
74 
75 class AccountService : public Node
76 {
77   public:
78     AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/")
79     {
80         entityPrivileges = {
81             {boost::beast::http::verb::get,
82              {{"ConfigureUsers"}, {"ConfigureManager"}}},
83             {boost::beast::http::verb::head, {{"Login"}}},
84             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
85             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
86             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
87             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
88     }
89 
90   private:
91     void doGet(crow::Response& res, const crow::Request& req,
92                const std::vector<std::string>& params) override
93     {
94         auto asyncResp = std::make_shared<AsyncResp>(res);
95         res.jsonValue = {
96             {"@odata.context", "/redfish/v1/"
97                                "$metadata#AccountService.AccountService"},
98             {"@odata.id", "/redfish/v1/AccountService"},
99             {"@odata.type", "#AccountService."
100                             "v1_1_0.AccountService"},
101             {"Id", "AccountService"},
102             {"Name", "Account Service"},
103             {"Description", "Account Service"},
104             {"ServiceEnabled", true},
105             {"MaxPasswordLength", 31},
106             {"Accounts",
107              {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}},
108             {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}};
109 
110         crow::connections::systemBus->async_method_call(
111             [asyncResp](
112                 const boost::system::error_code ec,
113                 const std::vector<std::pair<
114                     std::string, std::variant<uint32_t, uint16_t, uint8_t>>>&
115                     propertiesList) {
116                 if (ec)
117                 {
118                     messages::internalError(asyncResp->res);
119                     return;
120                 }
121                 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
122                                  << "properties for AccountService";
123                 for (const std::pair<std::string,
124                                      std::variant<uint32_t, uint16_t, uint8_t>>&
125                          property : propertiesList)
126                 {
127                     if (property.first == "MinPasswordLength")
128                     {
129                         const uint8_t* value =
130                             std::get_if<uint8_t>(&property.second);
131                         if (value != nullptr)
132                         {
133                             asyncResp->res.jsonValue["MinPasswordLength"] =
134                                 *value;
135                         }
136                     }
137                     if (property.first == "AccountUnlockTimeout")
138                     {
139                         const uint32_t* value =
140                             std::get_if<uint32_t>(&property.second);
141                         if (value != nullptr)
142                         {
143                             asyncResp->res.jsonValue["AccountLockoutDuration"] =
144                                 *value;
145                         }
146                     }
147                     if (property.first == "MaxLoginAttemptBeforeLockout")
148                     {
149                         const uint16_t* value =
150                             std::get_if<uint16_t>(&property.second);
151                         if (value != nullptr)
152                         {
153                             asyncResp->res
154                                 .jsonValue["AccountLockoutThreshold"] = *value;
155                         }
156                     }
157                 }
158             },
159             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
160             "org.freedesktop.DBus.Properties", "GetAll",
161             "xyz.openbmc_project.User.AccountPolicy");
162     }
163     void doPatch(crow::Response& res, const crow::Request& req,
164                  const std::vector<std::string>& params) override
165     {
166         auto asyncResp = std::make_shared<AsyncResp>(res);
167 
168         std::optional<uint32_t> unlockTimeout;
169         std::optional<uint16_t> lockoutThreshold;
170         std::optional<uint16_t> minPasswordLength;
171         std::optional<uint16_t> maxPasswordLength;
172 
173         if (!json_util::readJson(req, res, "AccountLockoutDuration",
174                                  unlockTimeout, "AccountLockoutThreshold",
175                                  lockoutThreshold, "MaxPasswordLength",
176                                  maxPasswordLength, "MinPasswordLength",
177                                  minPasswordLength))
178         {
179             return;
180         }
181 
182         if (minPasswordLength)
183         {
184             messages::propertyNotWritable(asyncResp->res, "MinPasswordLength");
185         }
186 
187         if (maxPasswordLength)
188         {
189             messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
190         }
191 
192         if (unlockTimeout)
193         {
194             crow::connections::systemBus->async_method_call(
195                 [asyncResp](const boost::system::error_code ec) {
196                     if (ec)
197                     {
198                         messages::internalError(asyncResp->res);
199                         return;
200                     }
201                     messages::success(asyncResp->res);
202                 },
203                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
204                 "org.freedesktop.DBus.Properties", "Set",
205                 "xyz.openbmc_project.User.AccountPolicy",
206                 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout));
207         }
208         if (lockoutThreshold)
209         {
210             crow::connections::systemBus->async_method_call(
211                 [asyncResp](const boost::system::error_code ec) {
212                     if (ec)
213                     {
214                         messages::internalError(asyncResp->res);
215                         return;
216                     }
217                     messages::success(asyncResp->res);
218                 },
219                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
220                 "org.freedesktop.DBus.Properties", "Set",
221                 "xyz.openbmc_project.User.AccountPolicy",
222                 "MaxLoginAttemptBeforeLockout",
223                 std::variant<uint16_t>(*lockoutThreshold));
224         }
225     }
226 };
227 class AccountsCollection : public Node
228 {
229   public:
230     AccountsCollection(CrowApp& app) :
231         Node(app, "/redfish/v1/AccountService/Accounts/")
232     {
233         entityPrivileges = {
234             {boost::beast::http::verb::get,
235              {{"ConfigureUsers"}, {"ConfigureManager"}}},
236             {boost::beast::http::verb::head, {{"Login"}}},
237             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
238             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
239             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
240             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
241     }
242 
243   private:
244     void doGet(crow::Response& res, const crow::Request& req,
245                const std::vector<std::string>& params) override
246     {
247         auto asyncResp = std::make_shared<AsyncResp>(res);
248         res.jsonValue = {{"@odata.context",
249                           "/redfish/v1/"
250                           "$metadata#ManagerAccountCollection."
251                           "ManagerAccountCollection"},
252                          {"@odata.id", "/redfish/v1/AccountService/Accounts"},
253                          {"@odata.type", "#ManagerAccountCollection."
254                                          "ManagerAccountCollection"},
255                          {"Name", "Accounts Collection"},
256                          {"Description", "BMC User Accounts"}};
257 
258         crow::connections::systemBus->async_method_call(
259             [asyncResp](const boost::system::error_code ec,
260                         const ManagedObjectType& users) {
261                 if (ec)
262                 {
263                     messages::internalError(asyncResp->res);
264                     return;
265                 }
266 
267                 nlohmann::json& memberArray =
268                     asyncResp->res.jsonValue["Members"];
269                 memberArray = nlohmann::json::array();
270 
271                 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
272                 for (auto& user : users)
273                 {
274                     const std::string& path =
275                         static_cast<const std::string&>(user.first);
276                     std::size_t lastIndex = path.rfind("/");
277                     if (lastIndex == std::string::npos)
278                     {
279                         lastIndex = 0;
280                     }
281                     else
282                     {
283                         lastIndex += 1;
284                     }
285                     memberArray.push_back(
286                         {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
287                                            path.substr(lastIndex)}});
288                 }
289             },
290             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
291             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
292     }
293     void doPost(crow::Response& res, const crow::Request& req,
294                 const std::vector<std::string>& params) override
295     {
296         auto asyncResp = std::make_shared<AsyncResp>(res);
297 
298         std::string username;
299         std::string password;
300         std::optional<std::string> roleId("User");
301         std::optional<bool> enabled = true;
302         if (!json_util::readJson(req, res, "UserName", username, "Password",
303                                  password, "RoleId", roleId, "Enabled",
304                                  enabled))
305         {
306             return;
307         }
308 
309         std::string priv = getRoleIdFromPrivilege(*roleId);
310         if (priv.empty())
311         {
312             messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
313             return;
314         }
315         roleId = priv;
316 
317         crow::connections::systemBus->async_method_call(
318             [asyncResp, username, password{std::move(password)}](
319                 const boost::system::error_code ec) {
320                 if (ec)
321                 {
322                     messages::resourceAlreadyExists(
323                         asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
324                         "UserName", username);
325                     return;
326                 }
327 
328                 if (!pamUpdatePassword(username, password))
329                 {
330                     // At this point we have a user that's been created, but the
331                     // password set failed.  Something is wrong, so delete the
332                     // user that we've already created
333                     crow::connections::systemBus->async_method_call(
334                         [asyncResp](const boost::system::error_code ec) {
335                             if (ec)
336                             {
337                                 messages::internalError(asyncResp->res);
338                                 return;
339                             }
340 
341                             messages::invalidObject(asyncResp->res, "Password");
342                         },
343                         "xyz.openbmc_project.User.Manager",
344                         "/xyz/openbmc_project/user/" + username,
345                         "xyz.openbmc_project.Object.Delete", "Delete");
346 
347                     BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
348                     return;
349                 }
350 
351                 messages::created(asyncResp->res);
352                 asyncResp->res.addHeader(
353                     "Location",
354                     "/redfish/v1/AccountService/Accounts/" + username);
355             },
356             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
357             "xyz.openbmc_project.User.Manager", "CreateUser", username,
358             std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"},
359             *roleId, *enabled);
360     }
361 };
362 
363 template <typename Callback>
364 inline void checkDbusPathExists(const std::string& path, Callback&& callback)
365 {
366     using GetObjectType =
367         std::vector<std::pair<std::string, std::vector<std::string>>>;
368 
369     crow::connections::systemBus->async_method_call(
370         [callback{std::move(callback)}](const boost::system::error_code ec,
371                                         const GetObjectType& object_names) {
372             callback(!ec && object_names.size() != 0);
373         },
374         "xyz.openbmc_project.ObjectMapper",
375         "/xyz/openbmc_project/object_mapper",
376         "xyz.openbmc_project.ObjectMapper", "GetObject", path,
377         std::array<std::string, 0>());
378 }
379 
380 class ManagerAccount : public Node
381 {
382   public:
383     ManagerAccount(CrowApp& app) :
384         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
385     {
386         entityPrivileges = {
387             {boost::beast::http::verb::get,
388              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
389             {boost::beast::http::verb::head, {{"Login"}}},
390             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
391             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
392             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
393             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
394     }
395 
396   private:
397     void doGet(crow::Response& res, const crow::Request& req,
398                const std::vector<std::string>& params) override
399     {
400         res.jsonValue = {
401             {"@odata.context",
402              "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
403             {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
404             {"Name", "User Account"},
405             {"Description", "User Account"},
406             {"Password", nullptr},
407             {"RoleId", "Administrator"}};
408 
409         auto asyncResp = std::make_shared<AsyncResp>(res);
410 
411         if (params.size() != 1)
412         {
413             messages::internalError(asyncResp->res);
414             return;
415         }
416 
417         crow::connections::systemBus->async_method_call(
418             [asyncResp, accountName{std::string(params[0])}](
419                 const boost::system::error_code ec,
420                 const ManagedObjectType& users) {
421                 if (ec)
422                 {
423                     messages::internalError(asyncResp->res);
424                     return;
425                 }
426                 auto userIt = users.begin();
427 
428                 for (; userIt != users.end(); userIt++)
429                 {
430                     if (boost::ends_with(userIt->first.str, "/" + accountName))
431                     {
432                         break;
433                     }
434                 }
435                 if (userIt == users.end())
436                 {
437                     messages::resourceNotFound(asyncResp->res, "ManagerAccount",
438                                                accountName);
439                     return;
440                 }
441                 for (const auto& interface : userIt->second)
442                 {
443                     if (interface.first ==
444                         "xyz.openbmc_project.User.Attributes")
445                     {
446                         for (const auto& property : interface.second)
447                         {
448                             if (property.first == "UserEnabled")
449                             {
450                                 const bool* userEnabled =
451                                     std::get_if<bool>(&property.second);
452                                 if (userEnabled == nullptr)
453                                 {
454                                     BMCWEB_LOG_ERROR
455                                         << "UserEnabled wasn't a bool";
456                                     messages::internalError(asyncResp->res);
457                                     return;
458                                 }
459                                 asyncResp->res.jsonValue["Enabled"] =
460                                     *userEnabled;
461                             }
462                             else if (property.first ==
463                                      "UserLockedForFailedAttempt")
464                             {
465                                 const bool* userLocked =
466                                     std::get_if<bool>(&property.second);
467                                 if (userLocked == nullptr)
468                                 {
469                                     BMCWEB_LOG_ERROR << "UserLockedForF"
470                                                         "ailedAttempt "
471                                                         "wasn't a bool";
472                                     messages::internalError(asyncResp->res);
473                                     return;
474                                 }
475                                 asyncResp->res.jsonValue["Locked"] =
476                                     *userLocked;
477                                 asyncResp->res.jsonValue
478                                     ["Locked@Redfish.AllowableValues"] = {
479                                     false};
480                             }
481                             else if (property.first == "UserPrivilege")
482                             {
483                                 const std::string* userRolePtr =
484                                     std::get_if<std::string>(&property.second);
485                                 if (userRolePtr == nullptr)
486                                 {
487                                     BMCWEB_LOG_ERROR
488                                         << "UserPrivilege wasn't a "
489                                            "string";
490                                     messages::internalError(asyncResp->res);
491                                     return;
492                                 }
493                                 std::string priv =
494                                     getPrivilegeFromRoleId(*userRolePtr);
495                                 if (priv.empty())
496                                 {
497                                     BMCWEB_LOG_ERROR << "Invalid user role";
498                                     messages::internalError(asyncResp->res);
499                                     return;
500                                 }
501                                 asyncResp->res.jsonValue["RoleId"] = priv;
502 
503                                 asyncResp->res.jsonValue["Links"]["Role"] = {
504                                     {"@odata.id", "/redfish/v1/AccountService/"
505                                                   "Roles/" +
506                                                       priv}};
507                             }
508                         }
509                     }
510                 }
511 
512                 asyncResp->res.jsonValue["@odata.id"] =
513                     "/redfish/v1/AccountService/Accounts/" + accountName;
514                 asyncResp->res.jsonValue["Id"] = accountName;
515                 asyncResp->res.jsonValue["UserName"] = accountName;
516             },
517             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
518             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
519     }
520 
521     void doPatch(crow::Response& res, const crow::Request& req,
522                  const std::vector<std::string>& params) override
523     {
524         auto asyncResp = std::make_shared<AsyncResp>(res);
525         if (params.size() != 1)
526         {
527             messages::internalError(asyncResp->res);
528             return;
529         }
530 
531         std::optional<std::string> newUserName;
532         std::optional<std::string> password;
533         std::optional<bool> enabled;
534         std::optional<std::string> roleId;
535         std::optional<bool> locked;
536         if (!json_util::readJson(req, res, "UserName", newUserName, "Password",
537                                  password, "RoleId", roleId, "Enabled", enabled,
538                                  "Locked", locked))
539         {
540             return;
541         }
542 
543         const std::string& username = params[0];
544 
545         if (!newUserName)
546         {
547             // If the username isn't being updated, we can update the properties
548             // directly
549             updateUserProperties(asyncResp, username, password, enabled, roleId,
550                                  locked);
551             return;
552         }
553         else
554         {
555             crow::connections::systemBus->async_method_call(
556                 [this, asyncResp, username, password(std::move(password)),
557                  roleId(std::move(roleId)), enabled(std::move(enabled)),
558                  newUser{std::string(*newUserName)}, locked(std::move(locked))](
559                     const boost::system::error_code ec) {
560                     if (ec)
561                     {
562                         BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
563                         messages::resourceNotFound(
564                             asyncResp->res,
565                             "#ManagerAccount.v1_0_3.ManagerAccount", username);
566                         return;
567                     }
568 
569                     updateUserProperties(asyncResp, newUser, password, enabled,
570                                          roleId, locked);
571                 },
572                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
573                 "xyz.openbmc_project.User.Manager", "RenameUser", username,
574                 *newUserName);
575         }
576     }
577 
578     void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp,
579                               const std::string& username,
580                               std::optional<std::string> password,
581                               std::optional<bool> enabled,
582                               std::optional<std::string> roleId,
583                               std::optional<bool> locked)
584     {
585         if (password)
586         {
587             if (!pamUpdatePassword(username, *password))
588             {
589                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
590                 messages::internalError(asyncResp->res);
591                 return;
592             }
593         }
594 
595         std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username;
596         dbus::utility::escapePathForDbus(dbusObjectPath);
597 
598         checkDbusPathExists(
599             dbusObjectPath,
600             [dbusObjectPath(std::move(dbusObjectPath)), username,
601              password(std::move(password)), roleId(std::move(roleId)),
602              enabled(std::move(enabled)), locked(std::move(locked)),
603              asyncResp{std::move(asyncResp)}](int rc) {
604                 if (!rc)
605                 {
606                     messages::invalidObject(asyncResp->res, username.c_str());
607                     return;
608                 }
609                 if (enabled)
610                 {
611                     crow::connections::systemBus->async_method_call(
612                         [asyncResp](const boost::system::error_code ec) {
613                             if (ec)
614                             {
615                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
616                                                  << ec;
617                                 messages::internalError(asyncResp->res);
618                                 return;
619                             }
620                             messages::success(asyncResp->res);
621                             return;
622                         },
623                         "xyz.openbmc_project.User.Manager",
624                         dbusObjectPath.c_str(),
625                         "org.freedesktop.DBus.Properties", "Set",
626                         "xyz.openbmc_project.User.Attributes", "UserEnabled",
627                         std::variant<bool>{*enabled});
628                 }
629 
630                 if (roleId)
631                 {
632                     std::string priv = getRoleIdFromPrivilege(*roleId);
633                     if (priv.empty())
634                     {
635                         messages::propertyValueNotInList(asyncResp->res,
636                                                          *roleId, "RoleId");
637                         return;
638                     }
639 
640                     crow::connections::systemBus->async_method_call(
641                         [asyncResp](const boost::system::error_code ec) {
642                             if (ec)
643                             {
644                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
645                                                  << ec;
646                                 messages::internalError(asyncResp->res);
647                                 return;
648                             }
649                             messages::success(asyncResp->res);
650                         },
651                         "xyz.openbmc_project.User.Manager",
652                         dbusObjectPath.c_str(),
653                         "org.freedesktop.DBus.Properties", "Set",
654                         "xyz.openbmc_project.User.Attributes", "UserPrivilege",
655                         std::variant<std::string>{priv});
656                 }
657 
658                 if (locked)
659                 {
660                     // admin can unlock the account which is locked by
661                     // successive authentication failures but admin should not
662                     // be allowed to lock an account.
663                     if (*locked)
664                     {
665                         messages::propertyValueNotInList(asyncResp->res, "true",
666                                                          "Locked");
667                         return;
668                     }
669 
670                     crow::connections::systemBus->async_method_call(
671                         [asyncResp](const boost::system::error_code ec) {
672                             if (ec)
673                             {
674                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
675                                                  << ec;
676                                 messages::internalError(asyncResp->res);
677                                 return;
678                             }
679                             messages::success(asyncResp->res);
680                             return;
681                         },
682                         "xyz.openbmc_project.User.Manager",
683                         dbusObjectPath.c_str(),
684                         "org.freedesktop.DBus.Properties", "Set",
685                         "xyz.openbmc_project.User.Attributes",
686                         "UserLockedForFailedAttempt",
687                         sdbusplus::message::variant<bool>{*locked});
688                 }
689             });
690     }
691 
692     void doDelete(crow::Response& res, const crow::Request& req,
693                   const std::vector<std::string>& params) override
694     {
695         auto asyncResp = std::make_shared<AsyncResp>(res);
696 
697         if (params.size() != 1)
698         {
699             messages::internalError(asyncResp->res);
700             return;
701         }
702 
703         const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
704 
705         crow::connections::systemBus->async_method_call(
706             [asyncResp, username{std::move(params[0])}](
707                 const boost::system::error_code ec) {
708                 if (ec)
709                 {
710                     messages::resourceNotFound(
711                         asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
712                         username);
713                     return;
714                 }
715 
716                 messages::accountRemoved(asyncResp->res);
717             },
718             "xyz.openbmc_project.User.Manager", userPath,
719             "xyz.openbmc_project.Object.Delete", "Delete");
720     }
721 };
722 
723 } // namespace redfish
724