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