xref: /openbmc/bmcweb/include/dbus_privileges.hpp (revision cc67d0a0fed101c930b334a583d9ca9b222ceb77)
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.
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 
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 
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 
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 
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 
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