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