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