1 /* 2 // Copyright (c) 2020 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 #include "app.hpp" 18 #include "event_service_manager.hpp" 19 #include "generated/enums/event_service.hpp" 20 #include "http/utility.hpp" 21 #include "logging.hpp" 22 #include "query.hpp" 23 #include "registries/privilege_registry.hpp" 24 #include "snmp_trap_event_clients.hpp" 25 26 #include <boost/beast/http/fields.hpp> 27 #include <boost/system/error_code.hpp> 28 #include <boost/url/parse.hpp> 29 #include <sdbusplus/unpack_properties.hpp> 30 #include <utils/dbus_utils.hpp> 31 32 #include <charconv> 33 #include <memory> 34 #include <ranges> 35 #include <span> 36 #include <string> 37 38 namespace redfish 39 { 40 41 static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = { 42 eventFormatType, metricReportFormatType}; 43 static constexpr const std::array<const char*, 3> supportedRegPrefixes = { 44 "Base", "OpenBMC", "TaskEvent"}; 45 static constexpr const std::array<const char*, 3> supportedRetryPolicies = { 46 "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; 47 48 static constexpr const std::array<const char*, 1> supportedResourceTypes = { 49 "Task"}; 50 51 inline void requestRoutesEventService(App& app) 52 { 53 BMCWEB_ROUTE(app, "/redfish/v1/EventService/") 54 .privileges(redfish::privileges::getEventService) 55 .methods( 56 boost::beast::http::verb:: 57 get)([&app]( 58 const crow::Request& req, 59 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 60 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 61 { 62 return; 63 } 64 65 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService"; 66 asyncResp->res.jsonValue["@odata.type"] = 67 "#EventService.v1_5_0.EventService"; 68 asyncResp->res.jsonValue["Id"] = "EventService"; 69 asyncResp->res.jsonValue["Name"] = "Event Service"; 70 asyncResp->res.jsonValue["ServerSentEventUri"] = 71 "/redfish/v1/EventService/SSE"; 72 73 asyncResp->res.jsonValue["Subscriptions"]["@odata.id"] = 74 "/redfish/v1/EventService/Subscriptions"; 75 asyncResp->res.jsonValue["Actions"]["#EventService.SubmitTestEvent"] 76 ["target"] = 77 "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent"; 78 79 const persistent_data::EventServiceConfig eventServiceConfig = 80 persistent_data::EventServiceStore::getInstance() 81 .getEventServiceConfig(); 82 83 asyncResp->res.jsonValue["Status"]["State"] = 84 (eventServiceConfig.enabled ? "Enabled" : "Disabled"); 85 asyncResp->res.jsonValue["ServiceEnabled"] = 86 eventServiceConfig.enabled; 87 asyncResp->res.jsonValue["DeliveryRetryAttempts"] = 88 eventServiceConfig.retryAttempts; 89 asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] = 90 eventServiceConfig.retryTimeoutInterval; 91 asyncResp->res.jsonValue["EventFormatTypes"] = 92 supportedEvtFormatTypes; 93 asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes; 94 asyncResp->res.jsonValue["ResourceTypes"] = supportedResourceTypes; 95 96 nlohmann::json::object_t supportedSSEFilters; 97 supportedSSEFilters["EventFormatType"] = true; 98 supportedSSEFilters["MessageId"] = true; 99 supportedSSEFilters["MetricReportDefinition"] = true; 100 supportedSSEFilters["RegistryPrefix"] = true; 101 supportedSSEFilters["OriginResource"] = false; 102 supportedSSEFilters["ResourceType"] = false; 103 104 asyncResp->res.jsonValue["SSEFilterPropertiesSupported"] = 105 std::move(supportedSSEFilters); 106 }); 107 108 BMCWEB_ROUTE(app, "/redfish/v1/EventService/") 109 .privileges(redfish::privileges::patchEventService) 110 .methods(boost::beast::http::verb::patch)( 111 [&app](const crow::Request& req, 112 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 113 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 114 { 115 return; 116 } 117 std::optional<bool> serviceEnabled; 118 std::optional<uint32_t> retryAttemps; 119 std::optional<uint32_t> retryInterval; 120 121 if (!json_util::readJsonPatch( 122 req, asyncResp->res, "ServiceEnabled", serviceEnabled, 123 "DeliveryRetryAttempts", retryAttemps, 124 "DeliveryRetryIntervalSeconds", retryInterval)) 125 { 126 return; 127 } 128 129 persistent_data::EventServiceConfig eventServiceConfig = 130 persistent_data::EventServiceStore::getInstance() 131 .getEventServiceConfig(); 132 133 if (serviceEnabled) 134 { 135 eventServiceConfig.enabled = *serviceEnabled; 136 } 137 138 if (retryAttemps) 139 { 140 // Supported range [1-3] 141 if ((*retryAttemps < 1) || (*retryAttemps > 3)) 142 { 143 messages::queryParameterOutOfRange( 144 asyncResp->res, std::to_string(*retryAttemps), 145 "DeliveryRetryAttempts", "[1-3]"); 146 } 147 else 148 { 149 eventServiceConfig.retryAttempts = *retryAttemps; 150 } 151 } 152 153 if (retryInterval) 154 { 155 // Supported range [5 - 180] 156 if ((*retryInterval < 5) || (*retryInterval > 180)) 157 { 158 messages::queryParameterOutOfRange( 159 asyncResp->res, std::to_string(*retryInterval), 160 "DeliveryRetryIntervalSeconds", "[5-180]"); 161 } 162 else 163 { 164 eventServiceConfig.retryTimeoutInterval = 165 *retryInterval; 166 } 167 } 168 169 EventServiceManager::getInstance().setEventServiceConfig( 170 eventServiceConfig); 171 }); 172 } 173 174 inline void requestRoutesSubmitTestEvent(App& app) 175 { 176 BMCWEB_ROUTE( 177 app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/") 178 .privileges(redfish::privileges::postEventService) 179 .methods(boost::beast::http::verb::post)( 180 [&app](const crow::Request& req, 181 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 182 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 183 { 184 return; 185 } 186 if (!EventServiceManager::getInstance().sendTestEventLog()) 187 { 188 messages::serviceDisabled(asyncResp->res, 189 "/redfish/v1/EventService/"); 190 return; 191 } 192 asyncResp->res.result(boost::beast::http::status::no_content); 193 }); 194 } 195 196 inline void doSubscriptionCollection( 197 const boost::system::error_code& ec, 198 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 199 const dbus::utility::ManagedObjectType& resp) 200 { 201 if (ec) 202 { 203 if (ec.value() == EBADR || ec.value() == EHOSTUNREACH) 204 { 205 // This is an optional process so just return if it isn't there 206 return; 207 } 208 209 BMCWEB_LOG_ERROR("D-Bus response error on GetManagedObjects {}", ec); 210 messages::internalError(asyncResp->res); 211 return; 212 } 213 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 214 for (const auto& objpath : resp) 215 { 216 sdbusplus::message::object_path path(objpath.first); 217 const std::string snmpId = path.filename(); 218 if (snmpId.empty()) 219 { 220 BMCWEB_LOG_ERROR("The SNMP client ID is wrong"); 221 messages::internalError(asyncResp->res); 222 return; 223 } 224 225 getSnmpSubscriptionList(asyncResp, snmpId, memberArray); 226 } 227 } 228 229 inline void requestRoutesEventDestinationCollection(App& app) 230 { 231 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") 232 .privileges(redfish::privileges::getEventDestinationCollection) 233 .methods(boost::beast::http::verb::get)( 234 [&app](const crow::Request& req, 235 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 236 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 237 { 238 return; 239 } 240 asyncResp->res.jsonValue["@odata.type"] = 241 "#EventDestinationCollection.EventDestinationCollection"; 242 asyncResp->res.jsonValue["@odata.id"] = 243 "/redfish/v1/EventService/Subscriptions"; 244 asyncResp->res.jsonValue["Name"] = 245 "Event Destination Collections"; 246 247 nlohmann::json& memberArray = 248 asyncResp->res.jsonValue["Members"]; 249 250 std::vector<std::string> subscripIds = 251 EventServiceManager::getInstance().getAllIDs(); 252 memberArray = nlohmann::json::array(); 253 asyncResp->res.jsonValue["Members@odata.count"] = 254 subscripIds.size(); 255 256 for (const std::string& id : subscripIds) 257 { 258 nlohmann::json::object_t member; 259 member["@odata.id"] = boost::urls::format( 260 "/redfish/v1/EventService/Subscriptions/{}" + id); 261 memberArray.emplace_back(std::move(member)); 262 } 263 crow::connections::systemBus->async_method_call( 264 [asyncResp](const boost::system::error_code& ec, 265 const dbus::utility::ManagedObjectType& resp) { 266 doSubscriptionCollection(ec, asyncResp, resp); 267 }, 268 "xyz.openbmc_project.Network.SNMP", 269 "/xyz/openbmc_project/network/snmp/manager", 270 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 271 }); 272 273 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") 274 .privileges(redfish::privileges::postEventDestinationCollection) 275 .methods( 276 boost::beast::http::verb:: 277 post)([&app]( 278 const crow::Request& req, 279 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 280 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 281 { 282 return; 283 } 284 if (EventServiceManager::getInstance().getNumberOfSubscriptions() >= 285 maxNoOfSubscriptions) 286 { 287 messages::eventSubscriptionLimitExceeded(asyncResp->res); 288 return; 289 } 290 std::string destUrl; 291 std::string protocol; 292 std::optional<bool> verifyCertificate; 293 std::optional<std::string> context; 294 std::optional<std::string> subscriptionType; 295 std::optional<std::string> eventFormatType2; 296 std::optional<std::string> retryPolicy; 297 std::optional<std::vector<std::string>> msgIds; 298 std::optional<std::vector<std::string>> regPrefixes; 299 std::optional<std::vector<std::string>> resTypes; 300 std::optional<std::vector<nlohmann::json::object_t>> headers; 301 std::optional<std::vector<nlohmann::json::object_t>> mrdJsonArray; 302 303 if (!json_util::readJsonPatch( 304 req, asyncResp->res, "Destination", destUrl, "Context", 305 context, "Protocol", protocol, "SubscriptionType", 306 subscriptionType, "EventFormatType", eventFormatType2, 307 "HttpHeaders", headers, "RegistryPrefixes", regPrefixes, 308 "MessageIds", msgIds, "DeliveryRetryPolicy", retryPolicy, 309 "MetricReportDefinitions", mrdJsonArray, "ResourceTypes", 310 resTypes, "VerifyCertificate", verifyCertificate)) 311 { 312 return; 313 } 314 315 // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers 316 static constexpr const uint16_t maxDestinationSize = 2000; 317 if (destUrl.size() > maxDestinationSize) 318 { 319 messages::stringValueTooLong(asyncResp->res, "Destination", 320 maxDestinationSize); 321 return; 322 } 323 324 if (regPrefixes && msgIds) 325 { 326 if (!regPrefixes->empty() && !msgIds->empty()) 327 { 328 messages::propertyValueConflict( 329 asyncResp->res, "MessageIds", "RegistryPrefixes"); 330 return; 331 } 332 } 333 334 boost::system::result<boost::urls::url> url = 335 boost::urls::parse_absolute_uri(destUrl); 336 if (!url) 337 { 338 BMCWEB_LOG_WARNING( 339 "Failed to validate and split destination url"); 340 messages::propertyValueFormatError(asyncResp->res, destUrl, 341 "Destination"); 342 return; 343 } 344 url->normalize(); 345 crow::utility::setProtocolDefaults(*url, protocol); 346 crow::utility::setPortDefaults(*url); 347 348 if (url->path().empty()) 349 { 350 url->set_path("/"); 351 } 352 353 if (url->has_userinfo()) 354 { 355 messages::propertyValueFormatError(asyncResp->res, destUrl, 356 "Destination"); 357 return; 358 } 359 360 if (protocol == "SNMPv2c") 361 { 362 if (context) 363 { 364 messages::propertyValueConflict(asyncResp->res, "Context", 365 "Protocol"); 366 return; 367 } 368 if (eventFormatType2) 369 { 370 messages::propertyValueConflict( 371 asyncResp->res, "EventFormatType", "Protocol"); 372 return; 373 } 374 if (retryPolicy) 375 { 376 messages::propertyValueConflict(asyncResp->res, 377 "RetryPolicy", "Protocol"); 378 return; 379 } 380 if (msgIds) 381 { 382 messages::propertyValueConflict(asyncResp->res, 383 "MessageIds", "Protocol"); 384 return; 385 } 386 if (regPrefixes) 387 { 388 messages::propertyValueConflict( 389 asyncResp->res, "RegistryPrefixes", "Protocol"); 390 return; 391 } 392 if (resTypes) 393 { 394 messages::propertyValueConflict( 395 asyncResp->res, "ResourceTypes", "Protocol"); 396 return; 397 } 398 if (headers) 399 { 400 messages::propertyValueConflict(asyncResp->res, 401 "HttpHeaders", "Protocol"); 402 return; 403 } 404 if (mrdJsonArray) 405 { 406 messages::propertyValueConflict( 407 asyncResp->res, "MetricReportDefinitions", "Protocol"); 408 return; 409 } 410 if (url->scheme() != "snmp") 411 { 412 messages::propertyValueConflict(asyncResp->res, 413 "Destination", "Protocol"); 414 return; 415 } 416 417 addSnmpTrapClient(asyncResp, url->host_address(), 418 url->port_number()); 419 return; 420 } 421 422 std::shared_ptr<Subscription> subValue = 423 std::make_shared<Subscription>(*url, app.ioContext()); 424 425 subValue->destinationUrl = std::move(*url); 426 427 if (subscriptionType) 428 { 429 if (*subscriptionType != "RedfishEvent") 430 { 431 messages::propertyValueNotInList( 432 asyncResp->res, *subscriptionType, "SubscriptionType"); 433 return; 434 } 435 subValue->subscriptionType = *subscriptionType; 436 } 437 else 438 { 439 subValue->subscriptionType = "RedfishEvent"; // Default 440 } 441 442 if (protocol != "Redfish") 443 { 444 messages::propertyValueNotInList(asyncResp->res, protocol, 445 "Protocol"); 446 return; 447 } 448 subValue->protocol = protocol; 449 450 if (verifyCertificate) 451 { 452 subValue->verifyCertificate = *verifyCertificate; 453 } 454 455 if (eventFormatType2) 456 { 457 if (std::ranges::find(supportedEvtFormatTypes, 458 *eventFormatType2) == 459 supportedEvtFormatTypes.end()) 460 { 461 messages::propertyValueNotInList( 462 asyncResp->res, *eventFormatType2, "EventFormatType"); 463 return; 464 } 465 subValue->eventFormatType = *eventFormatType2; 466 } 467 else 468 { 469 // If not specified, use default "Event" 470 subValue->eventFormatType = "Event"; 471 } 472 473 if (context) 474 { 475 // This value is selected arbitrarily. 476 constexpr const size_t maxContextSize = 256; 477 if (context->size() > maxContextSize) 478 { 479 messages::stringValueTooLong(asyncResp->res, "Context", 480 maxContextSize); 481 return; 482 } 483 subValue->customText = *context; 484 } 485 486 if (headers) 487 { 488 size_t cumulativeLen = 0; 489 490 for (const nlohmann::json::object_t& headerChunk : *headers) 491 { 492 for (const auto& item : headerChunk) 493 { 494 const std::string* value = 495 item.second.get_ptr<const std::string*>(); 496 if (value == nullptr) 497 { 498 messages::propertyValueFormatError( 499 asyncResp->res, item.second, 500 "HttpHeaders/" + item.first); 501 return; 502 } 503 // Adding a new json value is the size of the key, + 504 // the size of the value + 2 * 2 quotes for each, + 505 // the colon and space between. example: 506 // "key": "value" 507 cumulativeLen += item.first.size() + value->size() + 6; 508 // This value is selected to mirror http_connection.hpp 509 constexpr const uint16_t maxHeaderSizeED = 8096; 510 if (cumulativeLen > maxHeaderSizeED) 511 { 512 messages::arraySizeTooLong( 513 asyncResp->res, "HttpHeaders", maxHeaderSizeED); 514 return; 515 } 516 subValue->httpHeaders.set(item.first, *value); 517 } 518 } 519 } 520 521 if (regPrefixes) 522 { 523 for (const std::string& it : *regPrefixes) 524 { 525 if (std::ranges::find(supportedRegPrefixes, it) == 526 supportedRegPrefixes.end()) 527 { 528 messages::propertyValueNotInList(asyncResp->res, it, 529 "RegistryPrefixes"); 530 return; 531 } 532 } 533 subValue->registryPrefixes = *regPrefixes; 534 } 535 536 if (resTypes) 537 { 538 for (const std::string& it : *resTypes) 539 { 540 if (std::ranges::find(supportedResourceTypes, it) == 541 supportedResourceTypes.end()) 542 { 543 messages::propertyValueNotInList(asyncResp->res, it, 544 "ResourceTypes"); 545 return; 546 } 547 } 548 subValue->resourceTypes = *resTypes; 549 } 550 551 if (msgIds) 552 { 553 std::vector<std::string> registryPrefix; 554 555 // If no registry prefixes are mentioned, consider all 556 // supported prefixes 557 if (subValue->registryPrefixes.empty()) 558 { 559 registryPrefix.assign(supportedRegPrefixes.begin(), 560 supportedRegPrefixes.end()); 561 } 562 else 563 { 564 registryPrefix = subValue->registryPrefixes; 565 } 566 567 for (const std::string& id : *msgIds) 568 { 569 bool validId = false; 570 571 // Check for Message ID in each of the selected Registry 572 for (const std::string& it : registryPrefix) 573 { 574 const std::span<const redfish::registries::MessageEntry> 575 registry = 576 redfish::registries::getRegistryFromPrefix(it); 577 578 if (std::ranges::any_of( 579 registry, 580 [&id](const redfish::registries::MessageEntry& 581 messageEntry) { 582 return id == messageEntry.first; 583 })) 584 { 585 validId = true; 586 break; 587 } 588 } 589 590 if (!validId) 591 { 592 messages::propertyValueNotInList(asyncResp->res, id, 593 "MessageIds"); 594 return; 595 } 596 } 597 598 subValue->registryMsgIds = *msgIds; 599 } 600 601 if (retryPolicy) 602 { 603 if (std::ranges::find(supportedRetryPolicies, *retryPolicy) == 604 supportedRetryPolicies.end()) 605 { 606 messages::propertyValueNotInList( 607 asyncResp->res, *retryPolicy, "DeliveryRetryPolicy"); 608 return; 609 } 610 subValue->retryPolicy = *retryPolicy; 611 } 612 else 613 { 614 // Default "TerminateAfterRetries" 615 subValue->retryPolicy = "TerminateAfterRetries"; 616 } 617 618 if (mrdJsonArray) 619 { 620 for (nlohmann::json::object_t& mrdObj : *mrdJsonArray) 621 { 622 std::string mrdUri; 623 624 if (!json_util::readJsonObject(mrdObj, asyncResp->res, 625 "@odata.id", mrdUri)) 626 627 { 628 return; 629 } 630 subValue->metricReportDefinitions.emplace_back(mrdUri); 631 } 632 } 633 634 std::string id = 635 EventServiceManager::getInstance().addPushSubscription( 636 subValue); 637 if (id.empty()) 638 { 639 messages::internalError(asyncResp->res); 640 return; 641 } 642 643 messages::created(asyncResp->res); 644 asyncResp->res.addHeader( 645 "Location", "/redfish/v1/EventService/Subscriptions/" + id); 646 }); 647 } 648 649 inline void requestRoutesEventDestination(App& app) 650 { 651 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 652 .privileges(redfish::privileges::getEventDestination) 653 .methods(boost::beast::http::verb::get)( 654 [&app](const crow::Request& req, 655 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 656 const std::string& param) { 657 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 658 { 659 return; 660 } 661 662 if (param.starts_with("snmp")) 663 { 664 getSnmpTrapClient(asyncResp, param); 665 return; 666 } 667 668 std::shared_ptr<Subscription> subValue = 669 EventServiceManager::getInstance().getSubscription(param); 670 if (subValue == nullptr) 671 { 672 asyncResp->res.result( 673 boost::beast::http::status::not_found); 674 return; 675 } 676 const std::string& id = param; 677 678 asyncResp->res.jsonValue["@odata.type"] = 679 "#EventDestination.v1_8_0.EventDestination"; 680 asyncResp->res.jsonValue["Protocol"] = 681 event_destination::EventDestinationProtocol::Redfish; 682 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 683 "/redfish/v1/EventService/Subscriptions/{}", id); 684 asyncResp->res.jsonValue["Id"] = id; 685 asyncResp->res.jsonValue["Name"] = "Event Destination " + id; 686 asyncResp->res.jsonValue["Destination"] = 687 subValue->destinationUrl; 688 asyncResp->res.jsonValue["Context"] = subValue->customText; 689 asyncResp->res.jsonValue["SubscriptionType"] = 690 subValue->subscriptionType; 691 asyncResp->res.jsonValue["HttpHeaders"] = 692 nlohmann::json::array(); 693 asyncResp->res.jsonValue["EventFormatType"] = 694 subValue->eventFormatType; 695 asyncResp->res.jsonValue["RegistryPrefixes"] = 696 subValue->registryPrefixes; 697 asyncResp->res.jsonValue["ResourceTypes"] = 698 subValue->resourceTypes; 699 700 asyncResp->res.jsonValue["MessageIds"] = 701 subValue->registryMsgIds; 702 asyncResp->res.jsonValue["DeliveryRetryPolicy"] = 703 subValue->retryPolicy; 704 asyncResp->res.jsonValue["VerifyCertificate"] = 705 subValue->verifyCertificate; 706 707 nlohmann::json::array_t mrdJsonArray; 708 for (const auto& mdrUri : subValue->metricReportDefinitions) 709 { 710 nlohmann::json::object_t mdr; 711 mdr["@odata.id"] = mdrUri; 712 mrdJsonArray.emplace_back(std::move(mdr)); 713 } 714 asyncResp->res.jsonValue["MetricReportDefinitions"] = 715 mrdJsonArray; 716 }); 717 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 718 // The below privilege is wrong, it should be ConfigureManager OR 719 // ConfigureSelf 720 // https://github.com/openbmc/bmcweb/issues/220 721 //.privileges(redfish::privileges::patchEventDestination) 722 .privileges({{"ConfigureManager"}}) 723 .methods(boost::beast::http::verb::patch)( 724 [&app](const crow::Request& req, 725 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 726 const std::string& param) { 727 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 728 { 729 return; 730 } 731 std::shared_ptr<Subscription> subValue = 732 EventServiceManager::getInstance().getSubscription(param); 733 if (subValue == nullptr) 734 { 735 asyncResp->res.result( 736 boost::beast::http::status::not_found); 737 return; 738 } 739 740 std::optional<std::string> context; 741 std::optional<std::string> retryPolicy; 742 std::optional<bool> verifyCertificate; 743 std::optional<std::vector<nlohmann::json::object_t>> headers; 744 745 if (!json_util::readJsonPatch( 746 req, asyncResp->res, "Context", context, 747 "VerifyCertificate", verifyCertificate, 748 "DeliveryRetryPolicy", retryPolicy, "HttpHeaders", 749 headers)) 750 { 751 return; 752 } 753 754 if (context) 755 { 756 subValue->customText = *context; 757 } 758 759 if (headers) 760 { 761 boost::beast::http::fields fields; 762 for (const nlohmann::json::object_t& headerChunk : *headers) 763 { 764 for (const auto& it : headerChunk) 765 { 766 const std::string* value = 767 it.second.get_ptr<const std::string*>(); 768 if (value == nullptr) 769 { 770 messages::propertyValueFormatError( 771 asyncResp->res, it.second, 772 "HttpHeaders/" + it.first); 773 return; 774 } 775 fields.set(it.first, *value); 776 } 777 } 778 subValue->httpHeaders = std::move(fields); 779 } 780 781 if (retryPolicy) 782 { 783 if (std::ranges::find(supportedRetryPolicies, 784 *retryPolicy) == 785 supportedRetryPolicies.end()) 786 { 787 messages::propertyValueNotInList(asyncResp->res, 788 *retryPolicy, 789 "DeliveryRetryPolicy"); 790 return; 791 } 792 subValue->retryPolicy = *retryPolicy; 793 } 794 795 if (verifyCertificate) 796 { 797 subValue->verifyCertificate = *verifyCertificate; 798 } 799 800 EventServiceManager::getInstance().updateSubscriptionData(); 801 }); 802 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 803 // The below privilege is wrong, it should be ConfigureManager OR 804 // ConfigureSelf 805 // https://github.com/openbmc/bmcweb/issues/220 806 //.privileges(redfish::privileges::deleteEventDestination) 807 .privileges({{"ConfigureManager"}}) 808 .methods(boost::beast::http::verb::delete_)( 809 [&app](const crow::Request& req, 810 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 811 const std::string& param) { 812 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 813 { 814 return; 815 } 816 817 if (param.starts_with("snmp")) 818 { 819 deleteSnmpTrapClient(asyncResp, param); 820 EventServiceManager::getInstance().deleteSubscription( 821 param); 822 return; 823 } 824 825 if (!EventServiceManager::getInstance().isSubscriptionExist( 826 param)) 827 { 828 asyncResp->res.result( 829 boost::beast::http::status::not_found); 830 return; 831 } 832 EventServiceManager::getInstance().deleteSubscription(param); 833 }); 834 } 835 836 } // namespace redfish 837