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