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