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