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