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