xref: /openbmc/bmcweb/include/dbus_privileges.hpp (revision bb1c7d30d6799b4e0fcd0b3fc4f97be9a253e6c0)
1  // SPDX-License-Identifier: Apache-2.0
2  // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3  #pragma once
4  
5  #include "async_resp.hpp"
6  #include "dbus_singleton.hpp"
7  #include "dbus_utility.hpp"
8  #include "error_messages.hpp"
9  #include "http_request.hpp"
10  #include "http_response.hpp"
11  #include "logging.hpp"
12  #include "privileges.hpp"
13  #include "routing/baserule.hpp"
14  #include "sessions.hpp"
15  #include "utils/dbus_utils.hpp"
16  
17  #include <boost/beast/http/status.hpp>
18  #include <boost/url/format.hpp>
19  #include <sdbusplus/unpack_properties.hpp>
20  
21  #include <functional>
22  #include <memory>
23  #include <optional>
24  #include <string>
25  #include <utility>
26  #include <vector>
27  
28  namespace crow
29  {
30  // Populate session with user information.
populateUserInfo(persistent_data::UserSession & session,const dbus::utility::DBusPropertiesMap & userInfoMap)31  inline bool populateUserInfo(
32      persistent_data::UserSession& session,
33      const dbus::utility::DBusPropertiesMap& userInfoMap)
34  {
35      std::string userRole;
36      bool remoteUser = false;
37      std::optional<bool> passwordExpired;
38      std::optional<std::vector<std::string>> userGroups;
39  
40      const bool success = sdbusplus::unpackPropertiesNoThrow(
41          redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, "UserPrivilege",
42          userRole, "RemoteUser", remoteUser, "UserPasswordExpired",
43          passwordExpired, "UserGroups", userGroups);
44  
45      if (!success)
46      {
47          BMCWEB_LOG_ERROR("Failed to unpack user properties.");
48          return false;
49      }
50  
51      if (!remoteUser && (!passwordExpired || !userGroups))
52      {
53          BMCWEB_LOG_ERROR(
54              "Missing UserPasswordExpired or UserGroups property for local user");
55          return false;
56      }
57  
58      session.userRole = userRole;
59      BMCWEB_LOG_DEBUG("userName = {} userRole = {}", session.username, userRole);
60  
61      // Set isConfigureSelfOnly based on D-Bus results.  This
62      // ignores the results from both pamAuthenticateUser and the
63      // value from any previous use of this session.
64      session.isConfigureSelfOnly = passwordExpired.value_or(false);
65  
66      session.userGroups.clear();
67      if (userGroups)
68      {
69          session.userGroups.swap(*userGroups);
70      }
71  
72      return true;
73  }
74  
isUserPrivileged(Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule)75  inline bool isUserPrivileged(
76      Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
77      BaseRule& rule)
78  {
79      if (req.session == nullptr)
80      {
81          return false;
82      }
83      // Get the user's privileges from the role
84      redfish::Privileges userPrivileges =
85          redfish::getUserPrivileges(*req.session);
86  
87      // Modify privileges if isConfigureSelfOnly.
88      if (req.session->isConfigureSelfOnly)
89      {
90          // Remove all privileges except ConfigureSelf
91          userPrivileges =
92              userPrivileges.intersection(redfish::Privileges{"ConfigureSelf"});
93          BMCWEB_LOG_DEBUG("Operation limited to ConfigureSelf");
94      }
95  
96      if (!rule.checkPrivileges(userPrivileges))
97      {
98          asyncResp->res.result(boost::beast::http::status::forbidden);
99          if (req.session->isConfigureSelfOnly)
100          {
101              redfish::messages::passwordChangeRequired(
102                  asyncResp->res,
103                  boost::urls::format("/redfish/v1/AccountService/Accounts/{}",
104                                      req.session->username));
105          }
106          return false;
107      }
108  
109      req.userRole = req.session->userRole;
110      return true;
111  }
112  
afterGetUserInfoValidate(Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule,const dbus::utility::DBusPropertiesMap & userInfoMap)113  inline bool afterGetUserInfoValidate(
114      Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
115      BaseRule& rule, const dbus::utility::DBusPropertiesMap& userInfoMap)
116  {
117      if (req.session == nullptr || !populateUserInfo(*req.session, userInfoMap))
118      {
119          BMCWEB_LOG_ERROR("Failed to populate user information");
120          asyncResp->res.result(
121              boost::beast::http::status::internal_server_error);
122          return false;
123      }
124  
125      if (!isUserPrivileged(req, asyncResp, rule))
126      {
127          // User is not privileged
128          BMCWEB_LOG_ERROR("Insufficient Privilege");
129          asyncResp->res.result(boost::beast::http::status::forbidden);
130          return false;
131      }
132  
133      return true;
134  }
135  
requestUserInfo(const std::string & username,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::move_only_function<void (const dbus::utility::DBusPropertiesMap &)> && callback)136  inline void requestUserInfo(
137      const std::string& username,
138      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
139      std::move_only_function<void(const dbus::utility::DBusPropertiesMap&)>&&
140          callback)
141  {
142      crow::connections::systemBus->async_method_call(
143          [asyncResp, callback = std::move(callback)](
144              const boost::system::error_code& ec,
145              const dbus::utility::DBusPropertiesMap& userInfoMap) mutable {
146              if (ec)
147              {
148                  BMCWEB_LOG_ERROR("GetUserInfo failed...");
149                  asyncResp->res.result(
150                      boost::beast::http::status::internal_server_error);
151                  return;
152              }
153              callback(userInfoMap);
154          },
155          "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
156          "xyz.openbmc_project.User.Manager", "GetUserInfo", username);
157  }
158  
validatePrivilege(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule,std::move_only_function<void ()> && callback)159  inline void validatePrivilege(
160      const std::shared_ptr<Request>& req,
161      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, BaseRule& rule,
162      std::move_only_function<void()>&& callback)
163  {
164      if (req->session == nullptr)
165      {
166          return;
167      }
168  
169      requestUserInfo(
170          req->session->username, asyncResp,
171          [req, asyncResp, &rule, callback = std::move(callback)](
172              const dbus::utility::DBusPropertiesMap& userInfoMap) mutable {
173              if (afterGetUserInfoValidate(*req, asyncResp, rule, userInfoMap))
174              {
175                  callback();
176              }
177          });
178  }
179  
getUserInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & username,std::shared_ptr<persistent_data::UserSession> & session,std::move_only_function<void ()> && callback)180  inline void getUserInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
181                          const std::string& username,
182                          std::shared_ptr<persistent_data::UserSession>& session,
183                          std::move_only_function<void()>&& callback)
184  {
185      requestUserInfo(
186          username, asyncResp,
187          [asyncResp, session, callback = std::move(callback)](
188              const dbus::utility::DBusPropertiesMap& userInfoMap) mutable {
189              if (!populateUserInfo(*session, userInfoMap))
190              {
191                  BMCWEB_LOG_ERROR("Failed to populate user information");
192                  asyncResp->res.result(
193                      boost::beast::http::status::internal_server_error);
194                  return;
195              }
196              callback();
197          });
198  }
199  
200  } // namespace crow
201