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 <error_messages.hpp>
20 #include <openbmc_dbus_rest.hpp>
21 #include <utils/json_utils.hpp>
22 
23 namespace redfish
24 {
25 
26 using ManagedObjectType = std::vector<std::pair<
27     sdbusplus::message::object_path,
28     boost::container::flat_map<
29         std::string, boost::container::flat_map<
30                          std::string, sdbusplus::message::variant<bool>>>>>;
31 
32 class AccountService : public Node
33 {
34   public:
35     AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/")
36     {
37         Node::json["@odata.id"] = "/redfish/v1/AccountService";
38         Node::json["@odata.type"] = "#AccountService.v1_1_0.AccountService";
39         Node::json["@odata.context"] =
40             "/redfish/v1/$metadata#AccountService.AccountService";
41         Node::json["Id"] = "AccountService";
42         Node::json["Description"] = "BMC User Accounts";
43         Node::json["Name"] = "Account Service";
44         Node::json["ServiceEnabled"] = true;
45         Node::json["MinPasswordLength"] = 1;
46         Node::json["MaxPasswordLength"] = 20;
47         Node::json["Accounts"]["@odata.id"] =
48             "/redfish/v1/AccountService/Accounts";
49         Node::json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
50 
51         entityPrivileges = {
52             {boost::beast::http::verb::get,
53              {{"ConfigureUsers"}, {"ConfigureManager"}}},
54             {boost::beast::http::verb::head, {{"Login"}}},
55             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
56             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
57             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
58             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
59     }
60 
61   private:
62     void doGet(crow::Response& res, const crow::Request& req,
63                const std::vector<std::string>& params) override
64     {
65         res.jsonValue = Node::json;
66         res.end();
67     }
68 };
69 class AccountsCollection : public Node
70 {
71   public:
72     AccountsCollection(CrowApp& app) :
73         Node(app, "/redfish/v1/AccountService/Accounts/")
74     {
75 
76         Node::json = {{"@odata.context", "/redfish/v1/"
77                                          "$metadata#ManagerAccountCollection."
78                                          "ManagerAccountCollection"},
79                       {"@odata.id", "/redfish/v1/AccountService/Accounts"},
80                       {"@odata.type", "#ManagerAccountCollection."
81                                       "ManagerAccountCollection"},
82                       {"Name", "Accounts Collection"},
83                       {"Description", "BMC User Accounts"}};
84 
85         entityPrivileges = {
86             {boost::beast::http::verb::get,
87              {{"ConfigureUsers"}, {"ConfigureManager"}}},
88             {boost::beast::http::verb::head, {{"Login"}}},
89             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
90             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
91             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
92             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
93     }
94 
95   private:
96     void doGet(crow::Response& res, const crow::Request& req,
97                const std::vector<std::string>& params) override
98     {
99         res.jsonValue = Node::json;
100         auto asyncResp = std::make_shared<AsyncResp>(res);
101         crow::connections::systemBus->async_method_call(
102             [asyncResp](const boost::system::error_code ec,
103                         const ManagedObjectType& users) {
104                 if (ec)
105                 {
106                     asyncResp->res.result(
107                         boost::beast::http::status::internal_server_error);
108                     return;
109                 }
110 
111                 nlohmann::json& memberArray =
112                     asyncResp->res.jsonValue["Members"];
113                 memberArray = nlohmann::json::array();
114 
115                 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
116                 for (auto& user : users)
117                 {
118                     const std::string& path =
119                         static_cast<const std::string&>(user.first);
120                     std::size_t lastIndex = path.rfind("/");
121                     if (lastIndex == std::string::npos)
122                     {
123                         lastIndex = 0;
124                     }
125                     else
126                     {
127                         lastIndex += 1;
128                     }
129                     memberArray.push_back(
130                         {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
131                                            path.substr(lastIndex)}});
132                 }
133             },
134             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
135             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
136     }
137     void doPost(crow::Response& res, const crow::Request& req,
138                 const std::vector<std::string>& params) override
139     {
140         auto asyncResp = std::make_shared<AsyncResp>(res);
141 
142         nlohmann::json patchRequest;
143         if (!json_util::processJsonFromRequest(res, req, patchRequest))
144         {
145             return;
146         }
147 
148         const std::string* username = nullptr;
149         const std::string* password = nullptr;
150         // Default to user
151         std::string privilege = "priv-user";
152         // default to enabled
153         bool enabled = true;
154         for (const auto& item : patchRequest.items())
155         {
156             if (item.key() == "UserName")
157             {
158                 username = item.value().get_ptr<const std::string*>();
159                 if (username == nullptr)
160                 {
161                     messages::addMessageToErrorJson(
162                         asyncResp->res.jsonValue,
163                         messages::propertyValueFormatError(item.value().dump(),
164                                                            item.key()));
165                     asyncResp->res.result(
166                         boost::beast::http::status::bad_request);
167                     return;
168                 }
169             }
170             else if (item.key() == "Enabled")
171             {
172                 const bool* enabledJson = item.value().get_ptr<const bool*>();
173                 if (enabledJson == nullptr)
174                 {
175                     messages::addMessageToErrorJson(
176                         asyncResp->res.jsonValue,
177                         messages::propertyValueFormatError(item.value().dump(),
178                                                            item.key()));
179                     asyncResp->res.result(
180                         boost::beast::http::status::bad_request);
181                     return;
182                 }
183                 enabled = *enabledJson;
184             }
185             else if (item.key() == "Password")
186             {
187                 password = item.value().get_ptr<const std::string*>();
188                 if (password == nullptr)
189                 {
190                     messages::addMessageToErrorJson(
191                         asyncResp->res.jsonValue,
192                         messages::propertyValueFormatError(item.value().dump(),
193                                                            item.key()));
194                     asyncResp->res.result(
195                         boost::beast::http::status::bad_request);
196                     return;
197                 }
198             }
199             else if (item.key() == "RoleId")
200             {
201                 const std::string* roleIdJson =
202                     item.value().get_ptr<const std::string*>();
203                 if (roleIdJson == nullptr)
204                 {
205                     messages::addMessageToErrorJson(
206                         asyncResp->res.jsonValue,
207                         messages::propertyValueFormatError(item.value().dump(),
208                                                            item.key()));
209                     asyncResp->res.result(
210                         boost::beast::http::status::bad_request);
211                     return;
212                 }
213                 const char* priv = getRoleIdFromPrivilege(*roleIdJson);
214                 if (priv == nullptr)
215                 {
216                     messages::addMessageToErrorJson(
217                         asyncResp->res.jsonValue,
218                         messages::propertyValueNotInList(*roleIdJson,
219                                                          item.key()));
220                     asyncResp->res.result(
221                         boost::beast::http::status::bad_request);
222                     return;
223                 }
224                 privilege = priv;
225             }
226             else
227             {
228                 messages::addMessageToErrorJson(
229                     asyncResp->res.jsonValue,
230                     messages::propertyNotWritable(item.key()));
231                 asyncResp->res.result(boost::beast::http::status::bad_request);
232                 return;
233             }
234         }
235 
236         if (username == nullptr)
237         {
238             messages::addMessageToErrorJson(
239                 asyncResp->res.jsonValue,
240                 messages::createFailedMissingReqProperties("UserName"));
241             asyncResp->res.result(boost::beast::http::status::bad_request);
242             return;
243         }
244 
245         if (password == nullptr)
246         {
247             messages::addMessageToErrorJson(
248                 asyncResp->res.jsonValue,
249                 messages::createFailedMissingReqProperties("Password"));
250             asyncResp->res.result(boost::beast::http::status::bad_request);
251             return;
252         }
253 
254         crow::connections::systemBus->async_method_call(
255             [asyncResp, username{std::string(*username)},
256              password{std::string(*password)}](
257                 const boost::system::error_code ec) {
258                 if (ec)
259                 {
260                     messages::addMessageToErrorJson(
261                         asyncResp->res.jsonValue,
262                         messages::resourceAlreadyExists(
263                             "#ManagerAccount.v1_0_3.ManagerAccount", "UserName",
264                             username));
265                     asyncResp->res.result(
266                         boost::beast::http::status::bad_request);
267                     return;
268                 }
269 
270                 if (!pamUpdatePassword(username, password))
271                 {
272                     // At this point we have a user that's been created, but the
273                     // password set failed.  Something is wrong, so delete the
274                     // user that we've already created
275                     crow::connections::systemBus->async_method_call(
276                         [asyncResp](const boost::system::error_code ec) {
277                             if (ec)
278                             {
279                                 asyncResp->res.result(
280                                     boost::beast::http::status::
281                                         internal_server_error);
282                                 return;
283                             }
284 
285                             asyncResp->res.result(
286                                 boost::beast::http::status::bad_request);
287                         },
288                         "xyz.openbmc_project.User.Manager",
289                         "/xyz/openbmc_project/user/" + username,
290                         "xyz.openbmc_project.Object.Delete", "Delete");
291 
292                     BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
293                     return;
294                 }
295 
296                 messages::addMessageToJsonRoot(asyncResp->res.jsonValue,
297                                                messages::created());
298                 asyncResp->res.addHeader(
299                     "Location",
300                     "/redfish/v1/AccountService/Accounts/" + username);
301             },
302             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
303             "xyz.openbmc_project.User.Manager", "CreateUser", *username,
304             std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"},
305             privilege, enabled);
306     }
307 
308     static const char* getRoleIdFromPrivilege(boost::beast::string_view role)
309     {
310         if (role == "Administrator")
311         {
312             return "priv-admin";
313         }
314         else if (role == "Callback")
315         {
316             return "priv-callback";
317         }
318         else if (role == "User")
319         {
320             return "priv-user";
321         }
322         else if (role == "Operator")
323         {
324             return "priv-operator";
325         }
326         return nullptr;
327     }
328 };
329 
330 template <typename Callback>
331 inline void checkDbusPathExists(const std::string& path, Callback&& callback)
332 {
333     using GetObjectType =
334         std::vector<std::pair<std::string, std::vector<std::string>>>;
335 
336     crow::connections::systemBus->async_method_call(
337         [callback{std::move(callback)}](const boost::system::error_code ec,
338                                         const GetObjectType& object_names) {
339             callback(ec || object_names.size() == 0);
340         },
341         "xyz.openbmc_project.ObjectMapper",
342         "/xyz/openbmc_project/object_mapper",
343         "xyz.openbmc_project.ObjectMapper", "GetObject", path,
344         std::array<std::string, 0>());
345 }
346 
347 class ManagerAccount : public Node
348 {
349   public:
350     ManagerAccount(CrowApp& app) :
351         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
352     {
353         Node::json = {{"@odata.context",
354                        "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
355                       {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
356 
357                       {"Name", "User Account"},
358                       {"Description", "User Account"},
359                       {"Password", nullptr},
360                       {"RoleId", "Administrator"},
361                       {"Links",
362                        {{"Role",
363                          {{"@odata.id", "/redfish/v1/AccountService/Roles/"
364                                         "Administrator"}}}}}};
365 
366         entityPrivileges = {
367             {boost::beast::http::verb::get,
368              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
369             {boost::beast::http::verb::head, {{"Login"}}},
370             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
371             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
372             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
373             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
374     }
375 
376   private:
377     void doGet(crow::Response& res, const crow::Request& req,
378                const std::vector<std::string>& params) override
379     {
380         res.jsonValue = Node::json;
381         auto asyncResp = std::make_shared<AsyncResp>(res);
382 
383         if (params.size() != 1)
384         {
385             res.result(boost::beast::http::status::internal_server_error);
386             return;
387         }
388 
389         crow::connections::systemBus->async_method_call(
390             [asyncResp, accountName{std::string(params[0])}](
391                 const boost::system::error_code ec,
392                 const ManagedObjectType& users) {
393                 if (ec)
394                 {
395                     asyncResp->res.result(
396                         boost::beast::http::status::internal_server_error);
397                     return;
398                 }
399 
400                 for (auto& user : users)
401                 {
402                     const std::string& path =
403                         static_cast<const std::string&>(user.first);
404                     std::size_t lastIndex = path.rfind("/");
405                     if (lastIndex == std::string::npos)
406                     {
407                         lastIndex = 0;
408                     }
409                     else
410                     {
411                         lastIndex += 1;
412                     }
413                     if (path.substr(lastIndex) == accountName)
414                     {
415                         for (const auto& interface : user.second)
416                         {
417                             if (interface.first ==
418                                 "xyz.openbmc_project.User.Attributes")
419                             {
420                                 for (const auto& property : interface.second)
421                                 {
422                                     if (property.first == "UserEnabled")
423                                     {
424                                         const bool* userEnabled =
425                                             mapbox::getPtr<const bool>(
426                                                 property.second);
427                                         if (userEnabled == nullptr)
428                                         {
429                                             BMCWEB_LOG_ERROR
430                                                 << "UserEnabled wasn't a bool";
431                                             continue;
432                                         }
433                                         asyncResp->res.jsonValue["Enabled"] =
434                                             *userEnabled;
435                                     }
436                                     else if (property.first ==
437                                              "UserLockedForFailedAttempt")
438                                     {
439                                         const bool* userLocked =
440                                             mapbox::getPtr<const bool>(
441                                                 property.second);
442                                         if (userLocked == nullptr)
443                                         {
444                                             BMCWEB_LOG_ERROR
445                                                 << "UserEnabled wasn't a bool";
446                                             continue;
447                                         }
448                                         asyncResp->res.jsonValue["Locked"] =
449                                             *userLocked;
450                                     }
451                                 }
452                             }
453                         }
454 
455                         asyncResp->res.jsonValue["@odata.id"] =
456                             "/redfish/v1/AccountService/Accounts/" +
457                             accountName;
458                         asyncResp->res.jsonValue["Id"] = accountName;
459                         asyncResp->res.jsonValue["UserName"] = accountName;
460 
461                         return;
462                     }
463                 }
464 
465                 asyncResp->res.result(boost::beast::http::status::not_found);
466             },
467             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
468             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
469     }
470 
471     void doPatch(crow::Response& res, const crow::Request& req,
472                  const std::vector<std::string>& params) override
473     {
474         auto asyncResp = std::make_shared<AsyncResp>(res);
475 
476         if (params.size() != 1)
477         {
478             res.result(boost::beast::http::status::internal_server_error);
479             return;
480         }
481 
482         nlohmann::json patchRequest;
483         if (!json_util::processJsonFromRequest(res, req, patchRequest))
484         {
485             return;
486         }
487 
488         // Check the user exists before updating the fields
489         checkDbusPathExists(
490             "/xyz/openbmc_project/users/" + params[0],
491             [username{std::string(params[0])},
492              patchRequest(std::move(patchRequest)),
493              asyncResp](bool userExists) {
494                 if (!userExists)
495                 {
496                     messages::addMessageToErrorJson(
497                         asyncResp->res.jsonValue,
498                         messages::resourceNotFound(
499                             "#ManagerAccount.v1_0_3.ManagerAccount", username));
500 
501                     asyncResp->res.result(
502                         boost::beast::http::status::not_found);
503                     return;
504                 }
505 
506                 for (const auto& item : patchRequest.items())
507                 {
508                     if (item.key() == "Password")
509                     {
510                         const std::string* passStr =
511                             item.value().get_ptr<const std::string*>();
512                         if (passStr == nullptr)
513                         {
514                             messages::addMessageToErrorJson(
515                                 asyncResp->res.jsonValue,
516                                 messages::propertyValueFormatError(
517                                     item.value().dump(), "Password"));
518                             return;
519                         }
520                         BMCWEB_LOG_DEBUG << "Updating user: " << username
521                                          << " to password " << *passStr;
522                         if (!pamUpdatePassword(username, *passStr))
523                         {
524                             BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
525                             asyncResp->res.result(boost::beast::http::status::
526                                                       internal_server_error);
527                             return;
528                         }
529                     }
530                     else if (item.key() == "Enabled")
531                     {
532                         const bool* enabledBool =
533                             item.value().get_ptr<const bool*>();
534 
535                         if (enabledBool == nullptr)
536                         {
537                             messages::addMessageToErrorJson(
538                                 asyncResp->res.jsonValue,
539                                 messages::propertyValueFormatError(
540                                     item.value().dump(), "Enabled"));
541                             return;
542                         }
543                         crow::connections::systemBus->async_method_call(
544                             [asyncResp](const boost::system::error_code ec) {
545                                 if (ec)
546                                 {
547                                     BMCWEB_LOG_ERROR
548                                         << "D-Bus responses error: " << ec;
549                                     asyncResp->res.result(
550                                         boost::beast::http::status::
551                                             internal_server_error);
552                                     return;
553                                 }
554                                 // TODO Consider support polling mechanism to
555                                 // verify status of host and chassis after
556                                 // execute the requested action.
557                                 BMCWEB_LOG_DEBUG << "Response with no content";
558                                 asyncResp->res.result(
559                                     boost::beast::http::status::no_content);
560                             },
561                             "xyz.openbmc_project.User.Manager",
562                             "/xyz/openbmc_project/users/" + username,
563                             "org.freedesktop.DBus.Properties", "Set",
564                             "xyz.openbmc_project.User.Attributes"
565                             "UserEnabled",
566                             sdbusplus::message::variant<bool>{*enabledBool});
567                     }
568                     else
569                     {
570                         messages::addMessageToErrorJson(
571                             asyncResp->res.jsonValue,
572                             messages::propertyNotWritable(item.key()));
573                         asyncResp->res.result(
574                             boost::beast::http::status::bad_request);
575                         return;
576                     }
577                 }
578             });
579     }
580 
581     void doDelete(crow::Response& res, const crow::Request& req,
582                   const std::vector<std::string>& params) override
583     {
584         auto asyncResp = std::make_shared<AsyncResp>(res);
585 
586         if (params.size() != 1)
587         {
588             res.result(boost::beast::http::status::internal_server_error);
589             return;
590         }
591 
592         const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
593 
594         crow::connections::systemBus->async_method_call(
595             [asyncResp, username{std::move(params[0])}](
596                 const boost::system::error_code ec) {
597                 if (ec)
598                 {
599                     messages::addMessageToErrorJson(
600                         asyncResp->res.jsonValue,
601                         messages::resourceNotFound(
602                             "#ManagerAccount.v1_0_3.ManagerAccount", username));
603                     asyncResp->res.result(
604                         boost::beast::http::status::not_found);
605                     return;
606                 }
607 
608                 messages::addMessageToJsonRoot(asyncResp->res.jsonValue,
609                                                messages::accountRemoved());
610             },
611             "xyz.openbmc_project.User.Manager", userPath,
612             "xyz.openbmc_project.Object.Delete", "Delete");
613     }
614 };
615 
616 } // namespace redfish
617