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