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
populateUserInfo(persistent_data::UserSession & session,const dbus::utility::DBusPropertiesMap & userInfoMap)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(Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule)67     isUserPrivileged(Request& req,
68                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
69                      BaseRule& rule)
70 {
71     if (req.session == nullptr)
72     {
73         return false;
74     }
75     // Get the user's privileges from the role
76     redfish::Privileges userPrivileges =
77         redfish::getUserPrivileges(*req.session);
78 
79     // Modify privileges if isConfigureSelfOnly.
80     if (req.session->isConfigureSelfOnly)
81     {
82         // Remove all privileges except ConfigureSelf
83         userPrivileges =
84             userPrivileges.intersection(redfish::Privileges{"ConfigureSelf"});
85         BMCWEB_LOG_DEBUG("Operation limited to ConfigureSelf");
86     }
87 
88     if (!rule.checkPrivileges(userPrivileges))
89     {
90         asyncResp->res.result(boost::beast::http::status::forbidden);
91         if (req.session->isConfigureSelfOnly)
92         {
93             redfish::messages::passwordChangeRequired(
94                 asyncResp->res,
95                 boost::urls::format("/redfish/v1/AccountService/Accounts/{}",
96                                     req.session->username));
97         }
98         return false;
99     }
100 
101     req.userRole = req.session->userRole;
102     return true;
103 }
104 
afterGetUserInfoValidate(Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule,const dbus::utility::DBusPropertiesMap & userInfoMap)105 inline bool afterGetUserInfoValidate(
106     Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
107     BaseRule& rule, const dbus::utility::DBusPropertiesMap& userInfoMap)
108 {
109     if (req.session == nullptr || !populateUserInfo(*req.session, userInfoMap))
110     {
111         BMCWEB_LOG_ERROR("Failed to populate user information");
112         asyncResp->res.result(
113             boost::beast::http::status::internal_server_error);
114         return false;
115     }
116 
117     if (!isUserPrivileged(req, asyncResp, rule))
118     {
119         // User is not privileged
120         BMCWEB_LOG_ERROR("Insufficient Privilege");
121         asyncResp->res.result(boost::beast::http::status::forbidden);
122         return false;
123     }
124 
125     return true;
126 }
127 
128 template <typename CallbackFn>
requestUserInfo(const std::string & username,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,CallbackFn && callback)129 void requestUserInfo(const std::string& username,
130                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
131                      CallbackFn&& callback)
132 {
133     crow::connections::systemBus->async_method_call(
134         [asyncResp, callback = std::forward<CallbackFn>(callback)](
135             const boost::system::error_code& ec,
136             const dbus::utility::DBusPropertiesMap& userInfoMap) mutable {
137         if (ec)
138         {
139             BMCWEB_LOG_ERROR("GetUserInfo failed...");
140             asyncResp->res.result(
141                 boost::beast::http::status::internal_server_error);
142             return;
143         }
144         callback(userInfoMap);
145     },
146         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
147         "xyz.openbmc_project.User.Manager", "GetUserInfo", username);
148 }
149 
150 template <typename CallbackFn>
validatePrivilege(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule,CallbackFn && callback)151 void validatePrivilege(const std::shared_ptr<Request>& req,
152                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
153                        BaseRule& rule, CallbackFn&& callback)
154 {
155     if (req->session == nullptr)
156     {
157         return;
158     }
159 
160     requestUserInfo(
161         req->session->username, asyncResp,
162         [req, asyncResp, &rule, callback = std::forward<CallbackFn>(callback)](
163             const dbus::utility::DBusPropertiesMap& userInfoMap) mutable {
164         if (afterGetUserInfoValidate(*req, asyncResp, rule, userInfoMap))
165         {
166             callback();
167         }
168     });
169 }
170 
171 template <typename CallbackFn>
getUserInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & username,std::shared_ptr<persistent_data::UserSession> & session,CallbackFn && callback)172 void getUserInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
173                  const std::string& username,
174                  std::shared_ptr<persistent_data::UserSession>& session,
175                  CallbackFn&& callback)
176 {
177     requestUserInfo(
178         username, asyncResp,
179         [asyncResp, session, callback = std::forward<CallbackFn>(callback)](
180             const dbus::utility::DBusPropertiesMap& userInfoMap) {
181         if (!populateUserInfo(*session, userInfoMap))
182         {
183             BMCWEB_LOG_ERROR("Failed to populate user information");
184             asyncResp->res.result(
185                 boost::beast::http::status::internal_server_error);
186             return;
187         }
188         callback();
189     });
190 }
191 
192 } // namespace crow
193