xref: /openbmc/bmcweb/features/redfish/lib/account_service.hpp (revision a840879df6c05bc68a9970d31009174c21114f6f)
188d16c9aSLewanczyk, Dawid /*
288d16c9aSLewanczyk, Dawid // Copyright (c) 2018 Intel Corporation
388d16c9aSLewanczyk, Dawid //
488d16c9aSLewanczyk, Dawid // Licensed under the Apache License, Version 2.0 (the "License");
588d16c9aSLewanczyk, Dawid // you may not use this file except in compliance with the License.
688d16c9aSLewanczyk, Dawid // You may obtain a copy of the License at
788d16c9aSLewanczyk, Dawid //
888d16c9aSLewanczyk, Dawid //      http://www.apache.org/licenses/LICENSE-2.0
988d16c9aSLewanczyk, Dawid //
1088d16c9aSLewanczyk, Dawid // Unless required by applicable law or agreed to in writing, software
1188d16c9aSLewanczyk, Dawid // distributed under the License is distributed on an "AS IS" BASIS,
1288d16c9aSLewanczyk, Dawid // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1388d16c9aSLewanczyk, Dawid // See the License for the specific language governing permissions and
1488d16c9aSLewanczyk, Dawid // limitations under the License.
1588d16c9aSLewanczyk, Dawid */
1688d16c9aSLewanczyk, Dawid #pragma once
1788d16c9aSLewanczyk, Dawid #include "node.hpp"
1888d16c9aSLewanczyk, Dawid 
19b9b2e0b2SEd Tanous #include <openbmc_dbus_rest.hpp>
20*a840879dSEd Tanous #include <utils/json_utils.hpp>
21b9b2e0b2SEd Tanous 
221abe55efSEd Tanous namespace redfish
231abe55efSEd Tanous {
2488d16c9aSLewanczyk, Dawid 
25b9b2e0b2SEd Tanous using ManagedObjectType = std::vector<std::pair<
26b9b2e0b2SEd Tanous     sdbusplus::message::object_path,
27b9b2e0b2SEd Tanous     boost::container::flat_map<
28b9b2e0b2SEd Tanous         std::string, boost::container::flat_map<
29b9b2e0b2SEd Tanous                          std::string, sdbusplus::message::variant<bool>>>>>;
30b9b2e0b2SEd Tanous 
311abe55efSEd Tanous class AccountService : public Node
321abe55efSEd Tanous {
3388d16c9aSLewanczyk, Dawid   public:
341abe55efSEd Tanous     AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/")
351abe55efSEd Tanous     {
36c1a46bd2SBorawski.Lukasz         Node::json["@odata.id"] = "/redfish/v1/AccountService";
37c1a46bd2SBorawski.Lukasz         Node::json["@odata.type"] = "#AccountService.v1_1_0.AccountService";
38c1a46bd2SBorawski.Lukasz         Node::json["@odata.context"] =
3988d16c9aSLewanczyk, Dawid             "/redfish/v1/$metadata#AccountService.AccountService";
40c1a46bd2SBorawski.Lukasz         Node::json["Id"] = "AccountService";
41c1a46bd2SBorawski.Lukasz         Node::json["Description"] = "BMC User Accounts";
42c1a46bd2SBorawski.Lukasz         Node::json["Name"] = "Account Service";
43c1a46bd2SBorawski.Lukasz         Node::json["ServiceEnabled"] = true;
44c1a46bd2SBorawski.Lukasz         Node::json["MinPasswordLength"] = 1;
45c1a46bd2SBorawski.Lukasz         Node::json["MaxPasswordLength"] = 20;
461abe55efSEd Tanous         Node::json["Accounts"]["@odata.id"] =
471abe55efSEd Tanous             "/redfish/v1/AccountService/Accounts";
48c1a46bd2SBorawski.Lukasz         Node::json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
493ebd75f7SEd Tanous 
503ebd75f7SEd Tanous         entityPrivileges = {
514b1b8683SBorawski.Lukasz             {boost::beast::http::verb::get,
524b1b8683SBorawski.Lukasz              {{"ConfigureUsers"}, {"ConfigureManager"}}},
53e0d918bcSEd Tanous             {boost::beast::http::verb::head, {{"Login"}}},
54e0d918bcSEd Tanous             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
55e0d918bcSEd Tanous             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
56e0d918bcSEd Tanous             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
57e0d918bcSEd Tanous             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
5888d16c9aSLewanczyk, Dawid     }
5988d16c9aSLewanczyk, Dawid 
6088d16c9aSLewanczyk, Dawid   private:
6155c7b7a2SEd Tanous     void doGet(crow::Response& res, const crow::Request& req,
621abe55efSEd Tanous                const std::vector<std::string>& params) override
631abe55efSEd Tanous     {
6455c7b7a2SEd Tanous         res.jsonValue = Node::json;
6588d16c9aSLewanczyk, Dawid         res.end();
6688d16c9aSLewanczyk, Dawid     }
6788d16c9aSLewanczyk, Dawid };
68b9b2e0b2SEd Tanous class AccountsCollection : public Node
69b9b2e0b2SEd Tanous {
70b9b2e0b2SEd Tanous   public:
71b9b2e0b2SEd Tanous     AccountsCollection(CrowApp& app) :
72b9b2e0b2SEd Tanous         Node(app, "/redfish/v1/AccountService/Accounts/")
73b9b2e0b2SEd Tanous     {
74b9b2e0b2SEd Tanous 
75b9b2e0b2SEd Tanous         Node::json = {{"@odata.context", "/redfish/v1/"
76b9b2e0b2SEd Tanous                                          "$metadata#ManagerAccountCollection."
77b9b2e0b2SEd Tanous                                          "ManagerAccountCollection"},
78b9b2e0b2SEd Tanous                       {"@odata.id", "/redfish/v1/AccountService/Accounts"},
79b9b2e0b2SEd Tanous                       {"@odata.type", "#ManagerAccountCollection."
80b9b2e0b2SEd Tanous                                       "ManagerAccountCollection"},
81b9b2e0b2SEd Tanous                       {"Name", "Accounts Collection"},
82b9b2e0b2SEd Tanous                       {"Description", "BMC User Accounts"}};
83b9b2e0b2SEd Tanous 
84b9b2e0b2SEd Tanous         entityPrivileges = {
85b9b2e0b2SEd Tanous             {boost::beast::http::verb::get,
86b9b2e0b2SEd Tanous              {{"ConfigureUsers"}, {"ConfigureManager"}}},
87b9b2e0b2SEd Tanous             {boost::beast::http::verb::head, {{"Login"}}},
88b9b2e0b2SEd Tanous             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
89b9b2e0b2SEd Tanous             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
90b9b2e0b2SEd Tanous             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
91b9b2e0b2SEd Tanous             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
92b9b2e0b2SEd Tanous     }
93b9b2e0b2SEd Tanous 
94b9b2e0b2SEd Tanous   private:
95b9b2e0b2SEd Tanous     void doGet(crow::Response& res, const crow::Request& req,
96b9b2e0b2SEd Tanous                const std::vector<std::string>& params) override
97b9b2e0b2SEd Tanous     {
98b9b2e0b2SEd Tanous         res.jsonValue = Node::json;
99b9b2e0b2SEd Tanous         auto asyncResp = std::make_shared<AsyncResp>(res);
100b9b2e0b2SEd Tanous         crow::connections::systemBus->async_method_call(
101b9b2e0b2SEd Tanous             [asyncResp](const boost::system::error_code ec,
102b9b2e0b2SEd Tanous                         const ManagedObjectType& users) {
103b9b2e0b2SEd Tanous                 if (ec)
104b9b2e0b2SEd Tanous                 {
105b9b2e0b2SEd Tanous                     asyncResp->res.result(
106b9b2e0b2SEd Tanous                         boost::beast::http::status::internal_server_error);
107b9b2e0b2SEd Tanous                     return;
108b9b2e0b2SEd Tanous                 }
109b9b2e0b2SEd Tanous 
110b9b2e0b2SEd Tanous                 nlohmann::json& memberArray =
111b9b2e0b2SEd Tanous                     asyncResp->res.jsonValue["Members"];
112b9b2e0b2SEd Tanous                 memberArray = nlohmann::json::array();
113b9b2e0b2SEd Tanous 
114b9b2e0b2SEd Tanous                 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
115b9b2e0b2SEd Tanous                 for (auto& user : users)
116b9b2e0b2SEd Tanous                 {
117b9b2e0b2SEd Tanous                     const std::string& path =
118b9b2e0b2SEd Tanous                         static_cast<const std::string&>(user.first);
119b9b2e0b2SEd Tanous                     std::size_t lastIndex = path.rfind("/");
120b9b2e0b2SEd Tanous                     if (lastIndex == std::string::npos)
121b9b2e0b2SEd Tanous                     {
122b9b2e0b2SEd Tanous                         lastIndex = 0;
123b9b2e0b2SEd Tanous                     }
124b9b2e0b2SEd Tanous                     else
125b9b2e0b2SEd Tanous                     {
126b9b2e0b2SEd Tanous                         lastIndex += 1;
127b9b2e0b2SEd Tanous                     }
128b9b2e0b2SEd Tanous                     memberArray.push_back(
129b9b2e0b2SEd Tanous                         {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
130b9b2e0b2SEd Tanous                                            path.substr(lastIndex)}});
131b9b2e0b2SEd Tanous                 }
132b9b2e0b2SEd Tanous             },
133b9b2e0b2SEd Tanous             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
134b9b2e0b2SEd Tanous             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
135b9b2e0b2SEd Tanous     }
136b9b2e0b2SEd Tanous };
137b9b2e0b2SEd Tanous 
138*a840879dSEd Tanous template <typename Callback>
139*a840879dSEd Tanous inline void checkDbusPathExists(const std::string& path, Callback&& callback)
140*a840879dSEd Tanous {
141*a840879dSEd Tanous     using GetObjectType =
142*a840879dSEd Tanous         std::vector<std::pair<std::string, std::vector<std::string>>>;
143*a840879dSEd Tanous 
144*a840879dSEd Tanous     crow::connections::systemBus->async_method_call(
145*a840879dSEd Tanous         [callback{std::move(callback)}](const boost::system::error_code ec,
146*a840879dSEd Tanous                                         const GetObjectType& object_names) {
147*a840879dSEd Tanous             callback(ec || object_names.size() == 0);
148*a840879dSEd Tanous         },
149*a840879dSEd Tanous         "xyz.openbmc_project.ObjectMapper",
150*a840879dSEd Tanous         "/xyz/openbmc_project/object_mapper",
151*a840879dSEd Tanous         "xyz.openbmc_project.ObjectMapper", "GetObject", path,
152*a840879dSEd Tanous         std::array<std::string, 0>());
153*a840879dSEd Tanous }
154*a840879dSEd Tanous 
155b9b2e0b2SEd Tanous class ManagerAccount : public Node
156b9b2e0b2SEd Tanous {
157b9b2e0b2SEd Tanous   public:
158b9b2e0b2SEd Tanous     ManagerAccount(CrowApp& app) :
159b9b2e0b2SEd Tanous         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
160b9b2e0b2SEd Tanous     {
161b9b2e0b2SEd Tanous         Node::json = {{"@odata.context",
162b9b2e0b2SEd Tanous                        "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
163b9b2e0b2SEd Tanous                       {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
164b9b2e0b2SEd Tanous 
165b9b2e0b2SEd Tanous                       {"Name", "User Account"},
166b9b2e0b2SEd Tanous                       {"Description", "User Account"},
167b9b2e0b2SEd Tanous                       {"Enabled", false},
168b9b2e0b2SEd Tanous                       {"Password", nullptr},
169b9b2e0b2SEd Tanous                       {"RoleId", "Administrator"},
170b9b2e0b2SEd Tanous                       {"Links",
171b9b2e0b2SEd Tanous                        {{"Role",
172b9b2e0b2SEd Tanous                          {{"@odata.id", "/redfish/v1/AccountService/Roles/"
173b9b2e0b2SEd Tanous                                         "Administrator"}}}}}};
174b9b2e0b2SEd Tanous 
175b9b2e0b2SEd Tanous         entityPrivileges = {
176b9b2e0b2SEd Tanous             {boost::beast::http::verb::get,
177b9b2e0b2SEd Tanous              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
178b9b2e0b2SEd Tanous             {boost::beast::http::verb::head, {{"Login"}}},
179b9b2e0b2SEd Tanous             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
180b9b2e0b2SEd Tanous             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
181b9b2e0b2SEd Tanous             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
182b9b2e0b2SEd Tanous             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
183b9b2e0b2SEd Tanous     }
184b9b2e0b2SEd Tanous 
185b9b2e0b2SEd Tanous   private:
186b9b2e0b2SEd Tanous     void doGet(crow::Response& res, const crow::Request& req,
187b9b2e0b2SEd Tanous                const std::vector<std::string>& params) override
188b9b2e0b2SEd Tanous     {
189b9b2e0b2SEd Tanous         res.jsonValue = Node::json;
190b9b2e0b2SEd Tanous         auto asyncResp = std::make_shared<AsyncResp>(res);
191b9b2e0b2SEd Tanous 
192b9b2e0b2SEd Tanous         if (params.size() != 1)
193b9b2e0b2SEd Tanous         {
194b9b2e0b2SEd Tanous             res.result(boost::beast::http::status::internal_server_error);
195b9b2e0b2SEd Tanous             return;
196b9b2e0b2SEd Tanous         }
197b9b2e0b2SEd Tanous 
198b9b2e0b2SEd Tanous         crow::connections::systemBus->async_method_call(
199b9b2e0b2SEd Tanous             [asyncResp, accountName{std::string(params[0])}](
200b9b2e0b2SEd Tanous                 const boost::system::error_code ec,
201b9b2e0b2SEd Tanous                 const ManagedObjectType& users) {
202b9b2e0b2SEd Tanous                 if (ec)
203b9b2e0b2SEd Tanous                 {
204b9b2e0b2SEd Tanous                     asyncResp->res.result(
205b9b2e0b2SEd Tanous                         boost::beast::http::status::internal_server_error);
206b9b2e0b2SEd Tanous                     return;
207b9b2e0b2SEd Tanous                 }
208b9b2e0b2SEd Tanous 
209b9b2e0b2SEd Tanous                 for (auto& user : users)
210b9b2e0b2SEd Tanous                 {
211b9b2e0b2SEd Tanous                     const std::string& path =
212b9b2e0b2SEd Tanous                         static_cast<const std::string&>(user.first);
213b9b2e0b2SEd Tanous                     std::size_t lastIndex = path.rfind("/");
214b9b2e0b2SEd Tanous                     if (lastIndex == std::string::npos)
215b9b2e0b2SEd Tanous                     {
216b9b2e0b2SEd Tanous                         lastIndex = 0;
217b9b2e0b2SEd Tanous                     }
218b9b2e0b2SEd Tanous                     else
219b9b2e0b2SEd Tanous                     {
220b9b2e0b2SEd Tanous                         lastIndex += 1;
221b9b2e0b2SEd Tanous                     }
222b9b2e0b2SEd Tanous                     if (path.substr(lastIndex) == accountName)
223b9b2e0b2SEd Tanous                     {
224b9b2e0b2SEd Tanous                         asyncResp->res.jsonValue["@odata.id"] =
225b9b2e0b2SEd Tanous                             "/redfish/v1/AccountService/Accounts/" +
226b9b2e0b2SEd Tanous                             accountName;
227b9b2e0b2SEd Tanous                         asyncResp->res.jsonValue["Id"] = accountName;
228b9b2e0b2SEd Tanous                         asyncResp->res.jsonValue["UserName"] = accountName;
229b9b2e0b2SEd Tanous 
230b9b2e0b2SEd Tanous                         return;
231b9b2e0b2SEd Tanous                     }
232b9b2e0b2SEd Tanous                 }
233b9b2e0b2SEd Tanous 
234b9b2e0b2SEd Tanous                 asyncResp->res.result(boost::beast::http::status::not_found);
235b9b2e0b2SEd Tanous             },
236b9b2e0b2SEd Tanous             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
237b9b2e0b2SEd Tanous             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
238b9b2e0b2SEd Tanous     }
239*a840879dSEd Tanous 
240*a840879dSEd Tanous     void doPatch(crow::Response& res, const crow::Request& req,
241*a840879dSEd Tanous                  const std::vector<std::string>& params) override
242*a840879dSEd Tanous     {
243*a840879dSEd Tanous         auto asyncResp = std::make_shared<AsyncResp>(res);
244*a840879dSEd Tanous 
245*a840879dSEd Tanous         if (params.size() != 1)
246*a840879dSEd Tanous         {
247*a840879dSEd Tanous             res.result(boost::beast::http::status::internal_server_error);
248*a840879dSEd Tanous             return;
249*a840879dSEd Tanous         }
250*a840879dSEd Tanous 
251*a840879dSEd Tanous         nlohmann::json patchRequest;
252*a840879dSEd Tanous         if (!json_util::processJsonFromRequest(res, req, patchRequest))
253*a840879dSEd Tanous         {
254*a840879dSEd Tanous             return;
255*a840879dSEd Tanous         }
256*a840879dSEd Tanous 
257*a840879dSEd Tanous         // Check the user exists before updating the fields
258*a840879dSEd Tanous         checkDbusPathExists(
259*a840879dSEd Tanous             "/xyz/openbmc_project/users/" + params[0],
260*a840879dSEd Tanous             [username{std::string(params[0])},
261*a840879dSEd Tanous              patchRequest(std::move(patchRequest)),
262*a840879dSEd Tanous              asyncResp](bool userExists) {
263*a840879dSEd Tanous                 if (!userExists)
264*a840879dSEd Tanous                 {
265*a840879dSEd Tanous                     messages::addMessageToErrorJson(
266*a840879dSEd Tanous                         asyncResp->res.jsonValue,
267*a840879dSEd Tanous                         messages::resourceNotFound(
268*a840879dSEd Tanous                             "#ManagerAccount.v1_0_3.ManagerAccount", username));
269*a840879dSEd Tanous 
270*a840879dSEd Tanous                     asyncResp->res.result(
271*a840879dSEd Tanous                         boost::beast::http::status::not_found);
272*a840879dSEd Tanous                     return;
273*a840879dSEd Tanous                 }
274*a840879dSEd Tanous 
275*a840879dSEd Tanous                 for (const auto& item : patchRequest.items())
276*a840879dSEd Tanous                 {
277*a840879dSEd Tanous                     if (item.key() == "Password")
278*a840879dSEd Tanous                     {
279*a840879dSEd Tanous                         const std::string* passStr =
280*a840879dSEd Tanous                             item.value().get_ptr<const std::string*>();
281*a840879dSEd Tanous                         if (passStr == nullptr)
282*a840879dSEd Tanous                         {
283*a840879dSEd Tanous                             messages::addMessageToErrorJson(
284*a840879dSEd Tanous                                 asyncResp->res.jsonValue,
285*a840879dSEd Tanous                                 messages::propertyValueFormatError(
286*a840879dSEd Tanous                                     item.value().dump(), "Password"));
287*a840879dSEd Tanous                             return;
288*a840879dSEd Tanous                         }
289*a840879dSEd Tanous                         BMCWEB_LOG_DEBUG << "Updating user: " << username
290*a840879dSEd Tanous                                          << " to password " << *passStr;
291*a840879dSEd Tanous                         if (!pamUpdatePassword(username, *passStr))
292*a840879dSEd Tanous                         {
293*a840879dSEd Tanous                             BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
294*a840879dSEd Tanous                             asyncResp->res.result(boost::beast::http::status::
295*a840879dSEd Tanous                                                       internal_server_error);
296*a840879dSEd Tanous                             return;
297*a840879dSEd Tanous                         }
298*a840879dSEd Tanous                     }
299*a840879dSEd Tanous                     else if (item.key() == "Enabled")
300*a840879dSEd Tanous                     {
301*a840879dSEd Tanous                         const bool* enabledBool =
302*a840879dSEd Tanous                             item.value().get_ptr<const bool*>();
303*a840879dSEd Tanous 
304*a840879dSEd Tanous                         if (enabledBool == nullptr)
305*a840879dSEd Tanous                         {
306*a840879dSEd Tanous                             messages::addMessageToErrorJson(
307*a840879dSEd Tanous                                 asyncResp->res.jsonValue,
308*a840879dSEd Tanous                                 messages::propertyValueFormatError(
309*a840879dSEd Tanous                                     item.value().dump(), "Enabled"));
310*a840879dSEd Tanous                             return;
311*a840879dSEd Tanous                         }
312*a840879dSEd Tanous                         crow::connections::systemBus->async_method_call(
313*a840879dSEd Tanous                             [asyncResp](const boost::system::error_code ec) {
314*a840879dSEd Tanous                                 if (ec)
315*a840879dSEd Tanous                                 {
316*a840879dSEd Tanous                                     BMCWEB_LOG_ERROR
317*a840879dSEd Tanous                                         << "D-Bus responses error: " << ec;
318*a840879dSEd Tanous                                     asyncResp->res.result(
319*a840879dSEd Tanous                                         boost::beast::http::status::
320*a840879dSEd Tanous                                             internal_server_error);
321*a840879dSEd Tanous                                     return;
322*a840879dSEd Tanous                                 }
323*a840879dSEd Tanous                                 // TODO Consider support polling mechanism to
324*a840879dSEd Tanous                                 // verify status of host and chassis after
325*a840879dSEd Tanous                                 // execute the requested action.
326*a840879dSEd Tanous                                 BMCWEB_LOG_DEBUG << "Response with no content";
327*a840879dSEd Tanous                                 asyncResp->res.result(
328*a840879dSEd Tanous                                     boost::beast::http::status::no_content);
329*a840879dSEd Tanous                             },
330*a840879dSEd Tanous                             "xyz.openbmc_project.User.Manager",
331*a840879dSEd Tanous                             "/xyz/openbmc_project/users/" + username,
332*a840879dSEd Tanous                             "org.freedesktop.DBus.Properties", "Set",
333*a840879dSEd Tanous                             "xyz.openbmc_project.User.Attributes"
334*a840879dSEd Tanous                             "UserEnabled",
335*a840879dSEd Tanous                             sdbusplus::message::variant<bool>{*enabledBool});
336*a840879dSEd Tanous                     }
337*a840879dSEd Tanous                     else
338*a840879dSEd Tanous                     {
339*a840879dSEd Tanous                         messages::addMessageToErrorJson(
340*a840879dSEd Tanous                             asyncResp->res.jsonValue,
341*a840879dSEd Tanous                             messages::propertyNotWritable(item.key()));
342*a840879dSEd Tanous                         asyncResp->res.result(
343*a840879dSEd Tanous                             boost::beast::http::status::bad_request);
344*a840879dSEd Tanous                         return;
345*a840879dSEd Tanous                     }
346*a840879dSEd Tanous                 }
347*a840879dSEd Tanous             });
348*a840879dSEd Tanous     }
349b9b2e0b2SEd Tanous };
35088d16c9aSLewanczyk, Dawid 
35188d16c9aSLewanczyk, Dawid } // namespace redfish
352