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