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