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