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 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 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> 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> 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> 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