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
requestUserInfo(const std::string & username,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::move_only_function<void (const dbus::utility::DBusPropertiesMap &)> && callback)135 inline void requestUserInfo(
136 const std::string& username,
137 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
138 std::move_only_function<void(const dbus::utility::DBusPropertiesMap&)>&&
139 callback)
140 {
141 dbus::utility::async_method_call(
142 asyncResp,
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
validatePrivilege(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,BaseRule & rule,std::move_only_function<void ()> && callback)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
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)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