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