1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "dbus_utility.hpp" 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 #include "routing/baserule.hpp" 11 #include "utils/dbus_utils.hpp" 12 13 #include <boost/url/format.hpp> 14 #include <sdbusplus/unpack_properties.hpp> 15 16 #include <memory> 17 #include <string> 18 #include <utility> 19 #include <vector> 20 21 namespace crow 22 { 23 // Populate session with user information. 24 inline bool 25 populateUserInfo(persistent_data::UserSession& session, 26 const dbus::utility::DBusPropertiesMap& userInfoMap) 27 { 28 std::string userRole; 29 bool remoteUser = false; 30 std::optional<bool> passwordExpired; 31 std::optional<std::vector<std::string>> userGroups; 32 33 const bool success = sdbusplus::unpackPropertiesNoThrow( 34 redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, "UserPrivilege", 35 userRole, "RemoteUser", remoteUser, "UserPasswordExpired", 36 passwordExpired, "UserGroups", userGroups); 37 38 if (!success) 39 { 40 BMCWEB_LOG_ERROR("Failed to unpack user properties."); 41 return false; 42 } 43 44 if (!remoteUser && (!passwordExpired || !userGroups)) 45 { 46 BMCWEB_LOG_ERROR( 47 "Missing UserPasswordExpired or UserGroups property for local user"); 48 return false; 49 } 50 51 session.userRole = userRole; 52 BMCWEB_LOG_DEBUG("userName = {} userRole = {}", session.username, userRole); 53 54 // Set isConfigureSelfOnly based on D-Bus results. This 55 // ignores the results from both pamAuthenticateUser and the 56 // value from any previous use of this session. 57 session.isConfigureSelfOnly = passwordExpired.value_or(false); 58 59 session.userGroups.clear(); 60 if (userGroups) 61 { 62 session.userGroups.swap(*userGroups); 63 } 64 65 return true; 66 } 67 68 inline bool isUserPrivileged( 69 Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 70 BaseRule& rule) 71 { 72 if (req.session == nullptr) 73 { 74 return false; 75 } 76 // Get the user's privileges from the role 77 redfish::Privileges userPrivileges = 78 redfish::getUserPrivileges(*req.session); 79 80 // Modify privileges if isConfigureSelfOnly. 81 if (req.session->isConfigureSelfOnly) 82 { 83 // Remove all privileges except ConfigureSelf 84 userPrivileges = 85 userPrivileges.intersection(redfish::Privileges{"ConfigureSelf"}); 86 BMCWEB_LOG_DEBUG("Operation limited to ConfigureSelf"); 87 } 88 89 if (!rule.checkPrivileges(userPrivileges)) 90 { 91 asyncResp->res.result(boost::beast::http::status::forbidden); 92 if (req.session->isConfigureSelfOnly) 93 { 94 redfish::messages::passwordChangeRequired( 95 asyncResp->res, 96 boost::urls::format("/redfish/v1/AccountService/Accounts/{}", 97 req.session->username)); 98 } 99 return false; 100 } 101 102 req.userRole = req.session->userRole; 103 return true; 104 } 105 106 inline bool afterGetUserInfoValidate( 107 Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 108 BaseRule& rule, const dbus::utility::DBusPropertiesMap& userInfoMap) 109 { 110 if (req.session == nullptr || !populateUserInfo(*req.session, userInfoMap)) 111 { 112 BMCWEB_LOG_ERROR("Failed to populate user information"); 113 asyncResp->res.result( 114 boost::beast::http::status::internal_server_error); 115 return false; 116 } 117 118 if (!isUserPrivileged(req, asyncResp, rule)) 119 { 120 // User is not privileged 121 BMCWEB_LOG_ERROR("Insufficient Privilege"); 122 asyncResp->res.result(boost::beast::http::status::forbidden); 123 return false; 124 } 125 126 return true; 127 } 128 129 template <typename CallbackFn> 130 void requestUserInfo(const std::string& username, 131 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 132 CallbackFn&& callback) 133 { 134 crow::connections::systemBus->async_method_call( 135 [asyncResp, callback = std::forward<CallbackFn>(callback)]( 136 const boost::system::error_code& ec, 137 const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { 138 if (ec) 139 { 140 BMCWEB_LOG_ERROR("GetUserInfo failed..."); 141 asyncResp->res.result( 142 boost::beast::http::status::internal_server_error); 143 return; 144 } 145 callback(userInfoMap); 146 }, 147 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 148 "xyz.openbmc_project.User.Manager", "GetUserInfo", username); 149 } 150 151 template <typename CallbackFn> 152 void validatePrivilege(const std::shared_ptr<Request>& req, 153 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 154 BaseRule& rule, CallbackFn&& callback) 155 { 156 if (req->session == nullptr) 157 { 158 return; 159 } 160 161 requestUserInfo( 162 req->session->username, asyncResp, 163 [req, asyncResp, &rule, callback = std::forward<CallbackFn>(callback)]( 164 const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { 165 if (afterGetUserInfoValidate(*req, asyncResp, rule, userInfoMap)) 166 { 167 callback(); 168 } 169 }); 170 } 171 172 template <typename CallbackFn> 173 void getUserInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 174 const std::string& username, 175 std::shared_ptr<persistent_data::UserSession>& session, 176 CallbackFn&& callback) 177 { 178 requestUserInfo( 179 username, asyncResp, 180 [asyncResp, session, callback = std::forward<CallbackFn>(callback)]( 181 const dbus::utility::DBusPropertiesMap& userInfoMap) { 182 if (!populateUserInfo(*session, userInfoMap)) 183 { 184 BMCWEB_LOG_ERROR("Failed to populate user information"); 185 asyncResp->res.result( 186 boost::beast::http::status::internal_server_error); 187 return; 188 } 189 callback(); 190 }); 191 } 192 193 } // namespace crow 194