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