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