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 // clang-format off 306 if (!json_util::readJsonPatch( 307 req, asyncResp->res, 308 "Context", context, 309 "DeliveryRetryPolicy", retryPolicy, 310 "Destination", destUrl, 311 "EventFormatType", eventFormatType2, 312 "HttpHeaders", headers, 313 "MessageIds", msgIds, 314 "MetricReportDefinitions", mrdJsonArray, 315 "OriginResources", originResources, 316 "Protocol", protocol, 317 "RegistryPrefixes", regPrefixes, 318 "ResourceTypes", resTypes, 319 "SubscriptionType", subscriptionType, 320 "VerifyCertificate", verifyCertificate 321 )) 322 { 323 return; 324 } 325 // clang-format on 326 327 // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers 328 static constexpr const uint16_t maxDestinationSize = 2000; 329 if (destUrl.size() > maxDestinationSize) 330 { 331 messages::stringValueTooLong(asyncResp->res, "Destination", 332 maxDestinationSize); 333 return; 334 } 335 336 if (regPrefixes && msgIds) 337 { 338 if (!regPrefixes->empty() && !msgIds->empty()) 339 { 340 messages::propertyValueConflict( 341 asyncResp->res, "MessageIds", "RegistryPrefixes"); 342 return; 343 } 344 } 345 346 boost::system::result<boost::urls::url> url = 347 boost::urls::parse_absolute_uri(destUrl); 348 if (!url) 349 { 350 BMCWEB_LOG_WARNING( 351 "Failed to validate and split destination url"); 352 messages::propertyValueFormatError(asyncResp->res, destUrl, 353 "Destination"); 354 return; 355 } 356 url->normalize(); 357 crow::utility::setProtocolDefaults(*url, protocol); 358 crow::utility::setPortDefaults(*url); 359 360 if (url->path().empty()) 361 { 362 url->set_path("/"); 363 } 364 365 if (url->has_userinfo()) 366 { 367 messages::propertyValueFormatError(asyncResp->res, destUrl, 368 "Destination"); 369 return; 370 } 371 372 if (protocol == "SNMPv2c") 373 { 374 if (context) 375 { 376 messages::propertyValueConflict(asyncResp->res, "Context", 377 "Protocol"); 378 return; 379 } 380 if (eventFormatType2) 381 { 382 messages::propertyValueConflict( 383 asyncResp->res, "EventFormatType", "Protocol"); 384 return; 385 } 386 if (retryPolicy) 387 { 388 messages::propertyValueConflict(asyncResp->res, 389 "RetryPolicy", "Protocol"); 390 return; 391 } 392 if (msgIds) 393 { 394 messages::propertyValueConflict(asyncResp->res, 395 "MessageIds", "Protocol"); 396 return; 397 } 398 if (regPrefixes) 399 { 400 messages::propertyValueConflict( 401 asyncResp->res, "RegistryPrefixes", "Protocol"); 402 return; 403 } 404 if (resTypes) 405 { 406 messages::propertyValueConflict( 407 asyncResp->res, "ResourceTypes", "Protocol"); 408 return; 409 } 410 if (headers) 411 { 412 messages::propertyValueConflict(asyncResp->res, 413 "HttpHeaders", "Protocol"); 414 return; 415 } 416 if (mrdJsonArray) 417 { 418 messages::propertyValueConflict( 419 asyncResp->res, "MetricReportDefinitions", "Protocol"); 420 return; 421 } 422 if (url->scheme() != "snmp") 423 { 424 messages::propertyValueConflict(asyncResp->res, 425 "Destination", "Protocol"); 426 return; 427 } 428 429 addSnmpTrapClient(asyncResp, url->host_address(), 430 url->port_number()); 431 return; 432 } 433 434 std::shared_ptr<Subscription> subValue = 435 std::make_shared<Subscription>(*url, app.ioContext()); 436 437 subValue->userSub.destinationUrl = std::move(*url); 438 439 if (subscriptionType) 440 { 441 if (*subscriptionType != "RedfishEvent") 442 { 443 messages::propertyValueNotInList( 444 asyncResp->res, *subscriptionType, "SubscriptionType"); 445 return; 446 } 447 subValue->userSub.subscriptionType = *subscriptionType; 448 } 449 else 450 { 451 // Default 452 subValue->userSub.subscriptionType = "RedfishEvent"; 453 } 454 455 if (protocol != "Redfish") 456 { 457 messages::propertyValueNotInList(asyncResp->res, protocol, 458 "Protocol"); 459 return; 460 } 461 subValue->userSub.protocol = protocol; 462 463 if (verifyCertificate) 464 { 465 subValue->userSub.verifyCertificate = *verifyCertificate; 466 } 467 468 if (eventFormatType2) 469 { 470 if (std::ranges::find(supportedEvtFormatTypes, 471 *eventFormatType2) == 472 supportedEvtFormatTypes.end()) 473 { 474 messages::propertyValueNotInList( 475 asyncResp->res, *eventFormatType2, "EventFormatType"); 476 return; 477 } 478 subValue->userSub.eventFormatType = *eventFormatType2; 479 } 480 else 481 { 482 // If not specified, use default "Event" 483 subValue->userSub.eventFormatType = "Event"; 484 } 485 486 if (context) 487 { 488 // This value is selected arbitrarily. 489 constexpr const size_t maxContextSize = 256; 490 if (context->size() > maxContextSize) 491 { 492 messages::stringValueTooLong(asyncResp->res, "Context", 493 maxContextSize); 494 return; 495 } 496 subValue->userSub.customText = *context; 497 } 498 499 if (headers) 500 { 501 size_t cumulativeLen = 0; 502 503 for (const nlohmann::json::object_t& headerChunk : *headers) 504 { 505 for (const auto& item : headerChunk) 506 { 507 const std::string* value = 508 item.second.get_ptr<const std::string*>(); 509 if (value == nullptr) 510 { 511 messages::propertyValueFormatError( 512 asyncResp->res, item.second, 513 "HttpHeaders/" + item.first); 514 return; 515 } 516 // Adding a new json value is the size of the key, + 517 // the size of the value + 2 * 2 quotes for each, + 518 // the colon and space between. example: 519 // "key": "value" 520 cumulativeLen += item.first.size() + value->size() + 6; 521 // This value is selected to mirror http_connection.hpp 522 constexpr const uint16_t maxHeaderSizeED = 8096; 523 if (cumulativeLen > maxHeaderSizeED) 524 { 525 messages::arraySizeTooLong( 526 asyncResp->res, "HttpHeaders", maxHeaderSizeED); 527 return; 528 } 529 subValue->userSub.httpHeaders.set(item.first, *value); 530 } 531 } 532 } 533 534 if (regPrefixes) 535 { 536 for (const std::string& it : *regPrefixes) 537 { 538 if (std::ranges::find(supportedRegPrefixes, it) == 539 supportedRegPrefixes.end()) 540 { 541 messages::propertyValueNotInList(asyncResp->res, it, 542 "RegistryPrefixes"); 543 return; 544 } 545 } 546 subValue->userSub.registryPrefixes = *regPrefixes; 547 } 548 549 if (originResources) 550 { 551 subValue->userSub.originResources = *originResources; 552 } 553 554 if (resTypes) 555 { 556 for (const std::string& it : *resTypes) 557 { 558 if (std::ranges::find(supportedResourceTypes, it) == 559 supportedResourceTypes.end()) 560 { 561 messages::propertyValueNotInList(asyncResp->res, it, 562 "ResourceTypes"); 563 return; 564 } 565 } 566 subValue->userSub.resourceTypes = *resTypes; 567 } 568 569 if (msgIds) 570 { 571 std::vector<std::string> registryPrefix; 572 573 // If no registry prefixes are mentioned, consider all 574 // supported prefixes 575 if (subValue->userSub.registryPrefixes.empty()) 576 { 577 registryPrefix.assign(supportedRegPrefixes.begin(), 578 supportedRegPrefixes.end()); 579 } 580 else 581 { 582 registryPrefix = subValue->userSub.registryPrefixes; 583 } 584 585 for (const std::string& id : *msgIds) 586 { 587 bool validId = false; 588 589 // Check for Message ID in each of the selected Registry 590 for (const std::string& it : registryPrefix) 591 { 592 const std::span<const redfish::registries::MessageEntry> 593 registry = 594 redfish::registries::getRegistryFromPrefix(it); 595 596 if (std::ranges::any_of( 597 registry, 598 [&id](const redfish::registries::MessageEntry& 599 messageEntry) { 600 return id == messageEntry.first; 601 })) 602 { 603 validId = true; 604 break; 605 } 606 } 607 608 if (!validId) 609 { 610 messages::propertyValueNotInList(asyncResp->res, id, 611 "MessageIds"); 612 return; 613 } 614 } 615 616 subValue->userSub.registryMsgIds = *msgIds; 617 } 618 619 if (retryPolicy) 620 { 621 if (std::ranges::find(supportedRetryPolicies, *retryPolicy) == 622 supportedRetryPolicies.end()) 623 { 624 messages::propertyValueNotInList( 625 asyncResp->res, *retryPolicy, "DeliveryRetryPolicy"); 626 return; 627 } 628 subValue->userSub.retryPolicy = *retryPolicy; 629 } 630 else 631 { 632 // Default "TerminateAfterRetries" 633 subValue->userSub.retryPolicy = "TerminateAfterRetries"; 634 } 635 636 if (mrdJsonArray) 637 { 638 for (nlohmann::json::object_t& mrdObj : *mrdJsonArray) 639 { 640 std::string mrdUri; 641 642 if (!json_util::readJsonObject(mrdObj, asyncResp->res, 643 "@odata.id", mrdUri)) 644 645 { 646 return; 647 } 648 subValue->userSub.metricReportDefinitions.emplace_back( 649 mrdUri); 650 } 651 } 652 653 std::string id = 654 EventServiceManager::getInstance().addPushSubscription( 655 subValue); 656 if (id.empty()) 657 { 658 messages::internalError(asyncResp->res); 659 return; 660 } 661 662 messages::created(asyncResp->res); 663 asyncResp->res.addHeader( 664 "Location", "/redfish/v1/EventService/Subscriptions/" + id); 665 }); 666 } 667 668 inline void requestRoutesEventDestination(App& app) 669 { 670 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 671 .privileges(redfish::privileges::getEventDestination) 672 .methods(boost::beast::http::verb::get)( 673 [&app](const crow::Request& req, 674 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 675 const std::string& param) { 676 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 677 { 678 return; 679 } 680 681 if (param.starts_with("snmp")) 682 { 683 getSnmpTrapClient(asyncResp, param); 684 return; 685 } 686 687 std::shared_ptr<Subscription> subValue = 688 EventServiceManager::getInstance().getSubscription(param); 689 if (subValue == nullptr) 690 { 691 asyncResp->res.result( 692 boost::beast::http::status::not_found); 693 return; 694 } 695 const std::string& id = param; 696 697 const persistent_data::UserSubscription& userSub = 698 subValue->userSub; 699 700 nlohmann::json& jVal = asyncResp->res.jsonValue; 701 jVal["@odata.type"] = 702 "#EventDestination.v1_14_1.EventDestination"; 703 jVal["Protocol"] = 704 event_destination::EventDestinationProtocol::Redfish; 705 jVal["@odata.id"] = boost::urls::format( 706 "/redfish/v1/EventService/Subscriptions/{}", id); 707 jVal["Id"] = id; 708 jVal["Name"] = "Event Destination " + id; 709 jVal["Destination"] = userSub.destinationUrl; 710 jVal["Context"] = userSub.customText; 711 jVal["SubscriptionType"] = userSub.subscriptionType; 712 jVal["HttpHeaders"] = nlohmann::json::array(); 713 jVal["EventFormatType"] = userSub.eventFormatType; 714 jVal["RegistryPrefixes"] = userSub.registryPrefixes; 715 jVal["ResourceTypes"] = userSub.resourceTypes; 716 717 jVal["MessageIds"] = userSub.registryMsgIds; 718 jVal["DeliveryRetryPolicy"] = userSub.retryPolicy; 719 jVal["VerifyCertificate"] = userSub.verifyCertificate; 720 721 nlohmann::json::array_t mrdJsonArray; 722 for (const auto& mdrUri : userSub.metricReportDefinitions) 723 { 724 nlohmann::json::object_t mdr; 725 mdr["@odata.id"] = mdrUri; 726 mrdJsonArray.emplace_back(std::move(mdr)); 727 } 728 jVal["MetricReportDefinitions"] = mrdJsonArray; 729 }); 730 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 731 // The below privilege is wrong, it should be ConfigureManager OR 732 // ConfigureSelf 733 // https://github.com/openbmc/bmcweb/issues/220 734 //.privileges(redfish::privileges::patchEventDestination) 735 .privileges({{"ConfigureManager"}}) 736 .methods(boost::beast::http::verb::patch)( 737 [&app](const crow::Request& req, 738 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 739 const std::string& param) { 740 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 741 { 742 return; 743 } 744 std::shared_ptr<Subscription> subValue = 745 EventServiceManager::getInstance().getSubscription(param); 746 if (subValue == nullptr) 747 { 748 asyncResp->res.result( 749 boost::beast::http::status::not_found); 750 return; 751 } 752 753 std::optional<std::string> context; 754 std::optional<std::string> retryPolicy; 755 std::optional<bool> verifyCertificate; 756 std::optional<std::vector<nlohmann::json::object_t>> headers; 757 758 if (!json_util::readJsonPatch( 759 req, asyncResp->res, "Context", context, 760 "VerifyCertificate", verifyCertificate, 761 "DeliveryRetryPolicy", retryPolicy, "HttpHeaders", 762 headers)) 763 { 764 return; 765 } 766 767 if (context) 768 { 769 subValue->userSub.customText = *context; 770 } 771 772 if (headers) 773 { 774 boost::beast::http::fields fields; 775 for (const nlohmann::json::object_t& headerChunk : *headers) 776 { 777 for (const auto& it : headerChunk) 778 { 779 const std::string* value = 780 it.second.get_ptr<const std::string*>(); 781 if (value == nullptr) 782 { 783 messages::propertyValueFormatError( 784 asyncResp->res, it.second, 785 "HttpHeaders/" + it.first); 786 return; 787 } 788 fields.set(it.first, *value); 789 } 790 } 791 subValue->userSub.httpHeaders = std::move(fields); 792 } 793 794 if (retryPolicy) 795 { 796 if (std::ranges::find(supportedRetryPolicies, 797 *retryPolicy) == 798 supportedRetryPolicies.end()) 799 { 800 messages::propertyValueNotInList(asyncResp->res, 801 *retryPolicy, 802 "DeliveryRetryPolicy"); 803 return; 804 } 805 subValue->userSub.retryPolicy = *retryPolicy; 806 } 807 808 if (verifyCertificate) 809 { 810 subValue->userSub.verifyCertificate = *verifyCertificate; 811 } 812 813 EventServiceManager::getInstance().updateSubscriptionData(); 814 }); 815 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 816 // The below privilege is wrong, it should be ConfigureManager OR 817 // ConfigureSelf 818 // https://github.com/openbmc/bmcweb/issues/220 819 //.privileges(redfish::privileges::deleteEventDestination) 820 .privileges({{"ConfigureManager"}}) 821 .methods(boost::beast::http::verb::delete_)( 822 [&app](const crow::Request& req, 823 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 824 const std::string& param) { 825 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 826 { 827 return; 828 } 829 EventServiceManager& event = EventServiceManager::getInstance(); 830 if (param.starts_with("snmp")) 831 { 832 deleteSnmpTrapClient(asyncResp, param); 833 event.deleteSubscription(param); 834 return; 835 } 836 837 if (!event.deleteSubscription(param)) 838 { 839 messages::resourceNotFound(asyncResp->res, 840 "EventDestination", param); 841 return; 842 } 843 messages::success(asyncResp->res); 844 }); 845 } 846 847 } // namespace redfish 848