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