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