#pragma once #include "app.hpp" #include "async_resp.hpp" #include "error_messages.hpp" #include "event_service_manager.hpp" #include "ibm/locks.hpp" #include "resource_messages.hpp" #include "utils/json_utils.hpp" #include #include #include #include #include #include using SType = std::string; using SegmentFlags = std::vector>; using LockRequest = std::tuple; using LockRequests = std::vector; using Rc = std::pair>; using RcGetLockList = std::variant>>; using ListOfSessionIds = std::vector; namespace crow { namespace ibm_mc { constexpr const char* methodNotAllowedMsg = "Method Not Allowed"; constexpr const char* resourceNotFoundMsg = "Resource Not Found"; constexpr const char* contentNotAcceptableMsg = "Content Not Acceptable"; constexpr const char* internalServerError = "Internal Server Error"; constexpr size_t maxSaveareaDirSize = 25000000; // Allow save area dir size to be max 25MB constexpr size_t minSaveareaFileSize = 100; // Allow save area file size of minimum 100B constexpr size_t maxSaveareaFileSize = 500000; // Allow save area file size upto 500KB constexpr size_t maxBroadcastMsgSize = 1000; // Allow Broadcast message size upto 1KB inline void handleFilePut(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& fileID) { std::error_code ec; // Check the content-type of the request boost::beast::string_view contentType = req.getHeaderValue("content-type"); if (!boost::iequals(contentType, "application/octet-stream")) { asyncResp->res.result(boost::beast::http::status::not_acceptable); asyncResp->res.jsonValue["Description"] = contentNotAcceptableMsg; return; } BMCWEB_LOG_DEBUG( "File upload in application/octet-stream format. Continue.."); BMCWEB_LOG_DEBUG( "handleIbmPut: Request to create/update the save-area file"); std::string_view path = "/var/lib/bmcweb/ibm-management-console/configfiles"; if (!crow::ibm_utils::createDirectory(path)) { asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; return; } std::ofstream file; std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles"); // Get the current size of the savearea directory std::filesystem::recursive_directory_iterator iter(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to prepare save-area " "directory iterator. ec : {}", ec.message()); return; } std::uintmax_t saveAreaDirSize = 0; for (const auto& it : iter) { if (!std::filesystem::is_directory(it, ec)) { if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find save-area " "directory . ec : {}", ec.message()); return; } std::uintmax_t fileSize = std::filesystem::file_size(it, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find save-area " "file size inside the directory . ec : {}", ec.message()); return; } saveAreaDirSize += fileSize; } } BMCWEB_LOG_DEBUG("saveAreaDirSize: {}", saveAreaDirSize); // Get the file size getting uploaded const std::string& data = req.body(); BMCWEB_LOG_DEBUG("data length: {}", data.length()); if (data.length() < minSaveareaFileSize) { asyncResp->res.result(boost::beast::http::status::bad_request); asyncResp->res.jsonValue["Description"] = "File size is less than minimum allowed size[100B]"; return; } if (data.length() > maxSaveareaFileSize) { asyncResp->res.result(boost::beast::http::status::bad_request); asyncResp->res.jsonValue["Description"] = "File size exceeds maximum allowed size[500KB]"; return; } // Form the file path loc /= fileID; BMCWEB_LOG_DEBUG("Writing to the file: {}", loc.string()); // Check if the same file exists in the directory bool fileExists = std::filesystem::exists(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find if file exists. ec : {}", ec.message()); return; } std::uintmax_t newSizeToWrite = 0; if (fileExists) { // File exists. Get the current file size std::uintmax_t currentFileSize = std::filesystem::file_size(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find file size. ec : {}", ec.message()); return; } // Calculate the difference in the file size. // If the data.length is greater than the existing file size, then // calculate the difference. Else consider the delta size as zero - // because there is no increase in the total directory size. // We need to add the diff only if the incoming data is larger than the // existing filesize if (data.length() > currentFileSize) { newSizeToWrite = data.length() - currentFileSize; } BMCWEB_LOG_DEBUG("newSizeToWrite: {}", newSizeToWrite); } else { // This is a new file upload newSizeToWrite = data.length(); } // Calculate the total dir size before writing the new file BMCWEB_LOG_DEBUG("total new size: {}", saveAreaDirSize + newSizeToWrite); if ((saveAreaDirSize + newSizeToWrite) > maxSaveareaDirSize) { asyncResp->res.result(boost::beast::http::status::bad_request); asyncResp->res.jsonValue["Description"] = "File size does not fit in the savearea " "directory maximum allowed size[25MB]"; return; } file.open(loc, std::ofstream::out); // set the permission of the file to 600 std::filesystem::perms permission = std::filesystem::perms::owner_write | std::filesystem::perms::owner_read; std::filesystem::permissions(loc, permission); if (file.fail()) { BMCWEB_LOG_DEBUG("Error while opening the file for writing"); asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = "Error while creating the file"; return; } file << data; std::string origin = "/ibm/v1/Host/ConfigFiles/" + fileID; // Push an event if (fileExists) { BMCWEB_LOG_DEBUG("config file is updated"); asyncResp->res.jsonValue["Description"] = "File Updated"; } else { BMCWEB_LOG_DEBUG("config file is created"); asyncResp->res.jsonValue["Description"] = "File Created"; } } inline void handleConfigFileList(const std::shared_ptr& asyncResp) { std::vector pathObjList; std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles"); if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc)) { for (const auto& file : std::filesystem::directory_iterator(loc)) { const std::filesystem::path& pathObj = file.path(); if (std::filesystem::is_regular_file(pathObj)) { pathObjList.emplace_back("/ibm/v1/Host/ConfigFiles/" + pathObj.filename().string()); } } } asyncResp->res.jsonValue["@odata.type"] = "#IBMConfigFile.v1_0_0.IBMConfigFile"; asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/Host/ConfigFiles/"; asyncResp->res.jsonValue["Id"] = "ConfigFiles"; asyncResp->res.jsonValue["Name"] = "ConfigFiles"; asyncResp->res.jsonValue["Members"] = std::move(pathObjList); asyncResp->res.jsonValue["Actions"]["#IBMConfigFiles.DeleteAll"] = { {"target", "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll"}}; } inline void deleteConfigFiles(const std::shared_ptr& asyncResp) { std::error_code ec; std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles"); if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc)) { std::filesystem::remove_all(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("deleteConfigFiles: Failed to delete the " "config files directory. ec : {}", ec.message()); } } } inline void getLockServiceData(const std::shared_ptr& asyncResp) { asyncResp->res.jsonValue["@odata.type"] = "#LockService.v1_0_0.LockService"; asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/HMC/LockService/"; asyncResp->res.jsonValue["Id"] = "LockService"; asyncResp->res.jsonValue["Name"] = "LockService"; asyncResp->res.jsonValue["Actions"]["#LockService.AcquireLock"] = { {"target", "/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock"}}; asyncResp->res.jsonValue["Actions"]["#LockService.ReleaseLock"] = { {"target", "/ibm/v1/HMC/LockService/Actions/LockService.ReleaseLock"}}; asyncResp->res.jsonValue["Actions"]["#LockService.GetLockList"] = { {"target", "/ibm/v1/HMC/LockService/Actions/LockService.GetLockList"}}; } inline void handleFileGet(const std::shared_ptr& asyncResp, const std::string& fileID) { BMCWEB_LOG_DEBUG("HandleGet on SaveArea files on path: {}", fileID); std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID); if (!std::filesystem::exists(loc) || !std::filesystem::is_regular_file(loc)) { BMCWEB_LOG_WARNING("{} Not found", loc.string()); asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; return; } std::ifstream readfile(loc.string()); if (!readfile) { BMCWEB_LOG_WARNING("{} Not found", loc.string()); asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; return; } std::string contentDispositionParam = "attachment; filename=\"" + fileID + "\""; asyncResp->res.addHeader(boost::beast::http::field::content_disposition, contentDispositionParam); std::string fileData; fileData = {std::istreambuf_iterator(readfile), std::istreambuf_iterator()}; asyncResp->res.jsonValue["Data"] = fileData; } inline void handleFileDelete(const std::shared_ptr& asyncResp, const std::string& fileID) { std::string filePath("/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID); BMCWEB_LOG_DEBUG("Removing the file : {}", filePath); std::ifstream fileOpen(filePath.c_str()); if (static_cast(fileOpen)) { if (remove(filePath.c_str()) == 0) { BMCWEB_LOG_DEBUG("File removed!"); asyncResp->res.jsonValue["Description"] = "File Deleted"; } else { BMCWEB_LOG_ERROR("File not removed!"); asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; } } else { BMCWEB_LOG_WARNING("File not found!"); asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; } } inline void handleBroadcastService(const crow::Request& req, const std::shared_ptr& asyncResp) { std::string broadcastMsg; if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "Message", broadcastMsg)) { BMCWEB_LOG_DEBUG("Not a Valid JSON"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } if (broadcastMsg.size() > maxBroadcastMsgSize) { BMCWEB_LOG_ERROR("Message size exceeds maximum allowed size[1KB]"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } } inline void handleFileUrl(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& fileID) { if (req.method() == boost::beast::http::verb::put) { handleFilePut(req, asyncResp, fileID); return; } if (req.method() == boost::beast::http::verb::get) { handleFileGet(asyncResp, fileID); return; } if (req.method() == boost::beast::http::verb::delete_) { handleFileDelete(asyncResp, fileID); return; } } inline void handleAcquireLockAPI(const crow::Request& req, const std::shared_ptr& asyncResp, std::vector body) { LockRequests lockRequestStructure; for (auto& element : body) { std::string lockType; uint64_t resourceId = 0; SegmentFlags segInfo; std::vector segmentFlags; if (!redfish::json_util::readJson(element, asyncResp->res, "LockType", lockType, "ResourceID", resourceId, "SegmentFlags", segmentFlags)) { BMCWEB_LOG_DEBUG("Not a Valid JSON"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } BMCWEB_LOG_DEBUG("{}", lockType); BMCWEB_LOG_DEBUG("{}", resourceId); BMCWEB_LOG_DEBUG("Segment Flags are present"); for (auto& e : segmentFlags) { std::string lockFlags; uint32_t segmentLength = 0; if (!redfish::json_util::readJson(e, asyncResp->res, "LockFlag", lockFlags, "SegmentLength", segmentLength)) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } BMCWEB_LOG_DEBUG("Lockflag : {}", lockFlags); BMCWEB_LOG_DEBUG("SegmentLength : {}", segmentLength); segInfo.emplace_back(std::make_pair(lockFlags, segmentLength)); } lockRequestStructure.emplace_back(make_tuple( req.session->uniqueId, req.session->clientId.value_or(""), lockType, resourceId, segInfo)); } // print lock request into journal for (auto& i : lockRequestStructure) { BMCWEB_LOG_DEBUG("{}", std::get<0>(i)); BMCWEB_LOG_DEBUG("{}", std::get<1>(i)); BMCWEB_LOG_DEBUG("{}", std::get<2>(i)); BMCWEB_LOG_DEBUG("{}", std::get<3>(i)); for (const auto& p : std::get<4>(i)) { BMCWEB_LOG_DEBUG("{}, {}", p.first, p.second); } } const LockRequests& t = lockRequestStructure; auto varAcquireLock = crow::ibm_mc_lock::Lock::getInstance().acquireLock(t); if (varAcquireLock.first) { // Either validity failure of there is a conflict with itself auto validityStatus = std::get>(varAcquireLock.second); if ((!validityStatus.first) && (validityStatus.second == 0)) { BMCWEB_LOG_DEBUG("Not a Valid record"); BMCWEB_LOG_DEBUG("Bad json in request"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } if (validityStatus.first && (validityStatus.second == 1)) { BMCWEB_LOG_ERROR("There is a conflict within itself"); asyncResp->res.result(boost::beast::http::status::conflict); return; } } else { auto conflictStatus = std::get(varAcquireLock.second); if (!conflictStatus.first) { BMCWEB_LOG_DEBUG("There is no conflict with the locktable"); asyncResp->res.result(boost::beast::http::status::ok); auto var = std::get(conflictStatus.second); nlohmann::json returnJson; returnJson["id"] = var; asyncResp->res.jsonValue["TransactionID"] = var; return; } BMCWEB_LOG_DEBUG("There is a conflict with the lock table"); asyncResp->res.result(boost::beast::http::status::conflict); auto var = std::get>(conflictStatus.second); nlohmann::json returnJson; nlohmann::json segments; nlohmann::json myarray = nlohmann::json::array(); returnJson["TransactionID"] = var.first; returnJson["SessionID"] = std::get<0>(var.second); returnJson["HMCID"] = std::get<1>(var.second); returnJson["LockType"] = std::get<2>(var.second); returnJson["ResourceID"] = std::get<3>(var.second); for (const auto& i : std::get<4>(var.second)) { segments["LockFlag"] = i.first; segments["SegmentLength"] = i.second; myarray.push_back(segments); } returnJson["SegmentFlags"] = myarray; BMCWEB_LOG_ERROR("Conflicting lock record: {}", returnJson); asyncResp->res.jsonValue["Record"] = returnJson; return; } } inline void handleRelaseAllAPI(const crow::Request& req, const std::shared_ptr& asyncResp) { crow::ibm_mc_lock::Lock::getInstance().releaseLock(req.session->uniqueId); asyncResp->res.result(boost::beast::http::status::ok); } inline void handleReleaseLockAPI(const crow::Request& req, const std::shared_ptr& asyncResp, const std::vector& listTransactionIds) { BMCWEB_LOG_DEBUG("{}", listTransactionIds.size()); BMCWEB_LOG_DEBUG("Data is present"); for (unsigned int listTransactionId : listTransactionIds) { BMCWEB_LOG_DEBUG("{}", listTransactionId); } // validate the request ids auto varReleaselock = crow::ibm_mc_lock::Lock::getInstance().releaseLock( listTransactionIds, std::make_pair(req.session->clientId.value_or(""), req.session->uniqueId)); if (!varReleaselock.first) { // validation Failed BMCWEB_LOG_ERROR("handleReleaseLockAPI: validation failed"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } auto statusRelease = std::get(varReleaselock.second); if (statusRelease.first) { // The current hmc owns all the locks, so we already released // them return; } // valid rid, but the current hmc does not own all the locks BMCWEB_LOG_DEBUG("Current HMC does not own all the locks"); asyncResp->res.result(boost::beast::http::status::unauthorized); auto var = statusRelease.second; nlohmann::json returnJson; nlohmann::json segments; nlohmann::json myArray = nlohmann::json::array(); returnJson["TransactionID"] = var.first; returnJson["SessionID"] = std::get<0>(var.second); returnJson["HMCID"] = std::get<1>(var.second); returnJson["LockType"] = std::get<2>(var.second); returnJson["ResourceID"] = std::get<3>(var.second); for (const auto& i : std::get<4>(var.second)) { segments["LockFlag"] = i.first; segments["SegmentLength"] = i.second; myArray.push_back(segments); } returnJson["SegmentFlags"] = myArray; BMCWEB_LOG_DEBUG("handleReleaseLockAPI: lockrecord: {}", returnJson); asyncResp->res.jsonValue["Record"] = returnJson; } inline void handleGetLockListAPI(const std::shared_ptr& asyncResp, const ListOfSessionIds& listSessionIds) { BMCWEB_LOG_DEBUG("{}", listSessionIds.size()); auto status = crow::ibm_mc_lock::Lock::getInstance().getLockList(listSessionIds); auto var = std::get>>(status); nlohmann::json lockRecords = nlohmann::json::array(); for (const auto& transactionId : var) { for (const auto& lockRecord : transactionId.second) { nlohmann::json returnJson; returnJson["TransactionID"] = transactionId.first; returnJson["SessionID"] = std::get<0>(lockRecord); returnJson["HMCID"] = std::get<1>(lockRecord); returnJson["LockType"] = std::get<2>(lockRecord); returnJson["ResourceID"] = std::get<3>(lockRecord); nlohmann::json segments; nlohmann::json segmentInfoArray = nlohmann::json::array(); for (const auto& segment : std::get<4>(lockRecord)) { segments["LockFlag"] = segment.first; segments["SegmentLength"] = segment.second; segmentInfoArray.push_back(segments); } returnJson["SegmentFlags"] = segmentInfoArray; lockRecords.push_back(returnJson); } } asyncResp->res.result(boost::beast::http::status::ok); asyncResp->res.jsonValue["Records"] = lockRecords; } inline bool isValidConfigFileName(const std::string& fileName, crow::Response& res) { if (fileName.empty()) { BMCWEB_LOG_ERROR("Empty filename"); res.jsonValue["Description"] = "Empty file path in the url"; return false; } // ConfigFile name is allowed to take upper and lowercase letters, // numbers and hyphen std::size_t found = fileName.find_first_not_of( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"); if (found != std::string::npos) { BMCWEB_LOG_ERROR("Unsupported character in filename: {}", fileName); res.jsonValue["Description"] = "Unsupported character in filename"; return false; } // Check the filename length if (fileName.length() > 20) { BMCWEB_LOG_ERROR("Name must be maximum 20 characters. " "Input filename length is: {}", fileName.length()); res.jsonValue["Description"] = "Filename must be maximum 20 characters"; return false; } return true; } inline void requestRoutes(App& app) { // allowed only for admin BMCWEB_ROUTE(app, "/ibm/v1/") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { asyncResp->res.jsonValue["@odata.type"] = "#ibmServiceRoot.v1_0_0.ibmServiceRoot"; asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/"; asyncResp->res.jsonValue["Id"] = "IBM Rest RootService"; asyncResp->res.jsonValue["Name"] = "IBM Service Root"; asyncResp->res.jsonValue["ConfigFiles"]["@odata.id"] = "/ibm/v1/Host/ConfigFiles"; asyncResp->res.jsonValue["LockService"]["@odata.id"] = "/ibm/v1/HMC/LockService"; asyncResp->res.jsonValue["BroadcastService"]["@odata.id"] = "/ibm/v1/HMC/BroadcastService"; }); BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { handleConfigFileList(asyncResp); }); BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request&, const std::shared_ptr& asyncResp) { deleteConfigFiles(asyncResp); }); BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::put, boost::beast::http::verb::get, boost::beast::http::verb::delete_)( [](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& fileName) { BMCWEB_LOG_DEBUG("ConfigFile : {}", fileName); // Validate the incoming fileName if (!isValidConfigFileName(fileName, asyncResp->res)) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } handleFileUrl(req, asyncResp, fileName); }); BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { getLockServiceData(asyncResp); }); BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request& req, const std::shared_ptr& asyncResp) { std::vector body; if (!redfish::json_util::readJsonAction(req, asyncResp->res, "Request", body)) { BMCWEB_LOG_DEBUG("Not a Valid JSON"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } handleAcquireLockAPI(req, asyncResp, body); }); BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.ReleaseLock") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request& req, const std::shared_ptr& asyncResp) { std::string type; std::vector listTransactionIds; if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "Type", type, "TransactionIDs", listTransactionIds)) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } if (type == "Transaction") { handleReleaseLockAPI(req, asyncResp, listTransactionIds); } else if (type == "Session") { handleRelaseAllAPI(req, asyncResp); } else { BMCWEB_LOG_DEBUG(" Value of Type : {}is Not a Valid key", type); redfish::messages::propertyValueNotInList(asyncResp->res, type, "Type"); } }); BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.GetLockList") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request& req, const std::shared_ptr& asyncResp) { ListOfSessionIds listSessionIds; if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "SessionIDs", listSessionIds)) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } handleGetLockListAPI(asyncResp, listSessionIds); }); BMCWEB_ROUTE(app, "/ibm/v1/HMC/BroadcastService") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request& req, const std::shared_ptr& asyncResp) { handleBroadcastService(req, asyncResp); }); } } // namespace ibm_mc } // namespace crow