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 // From the Redfish spec on EventId 201 // A service can ignore this value and replace it with its own. 202 // note that this parameter is intentionally ignored 203 204 std::optional<std::string> eventId; 205 TestEvent testEvent; 206 // clang-format off 207 if (!json_util::readJsonAction( 208 req, asyncResp->res, 209 "EventGroupId", testEvent.eventGroupId, 210 "EventId", eventId, 211 "EventTimestamp", testEvent.eventTimestamp, 212 "Message", testEvent.message, 213 "MessageArgs", testEvent.messageArgs, 214 "MessageId", testEvent.messageId, 215 "OriginOfCondition", testEvent.originOfCondition, 216 "Resolution", testEvent.resolution, 217 "Severity", testEvent.severity)) 218 { 219 return; 220 } 221 // clang-format on 222 223 if (!EventServiceManager::getInstance().sendTestEventLog( 224 testEvent)) 225 { 226 messages::serviceDisabled(asyncResp->res, 227 "/redfish/v1/EventService/"); 228 return; 229 } 230 asyncResp->res.result(boost::beast::http::status::no_content); 231 }); 232 } 233 234 inline void doSubscriptionCollection( 235 const boost::system::error_code& ec, 236 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 237 const dbus::utility::ManagedObjectType& resp) 238 { 239 if (ec) 240 { 241 if (ec.value() == EBADR || ec.value() == EHOSTUNREACH) 242 { 243 // This is an optional process so just return if it isn't there 244 return; 245 } 246 247 BMCWEB_LOG_ERROR("D-Bus response error on GetManagedObjects {}", ec); 248 messages::internalError(asyncResp->res); 249 return; 250 } 251 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 252 for (const auto& objpath : resp) 253 { 254 sdbusplus::message::object_path path(objpath.first); 255 const std::string snmpId = path.filename(); 256 if (snmpId.empty()) 257 { 258 BMCWEB_LOG_ERROR("The SNMP client ID is wrong"); 259 messages::internalError(asyncResp->res); 260 return; 261 } 262 263 getSnmpSubscriptionList(asyncResp, snmpId, memberArray); 264 } 265 } 266 267 inline void requestRoutesEventDestinationCollection(App& app) 268 { 269 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") 270 .privileges(redfish::privileges::getEventDestinationCollection) 271 .methods(boost::beast::http::verb::get)( 272 [&app](const crow::Request& req, 273 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 274 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 275 { 276 return; 277 } 278 asyncResp->res.jsonValue["@odata.type"] = 279 "#EventDestinationCollection.EventDestinationCollection"; 280 asyncResp->res.jsonValue["@odata.id"] = 281 "/redfish/v1/EventService/Subscriptions"; 282 asyncResp->res.jsonValue["Name"] = 283 "Event Destination Collections"; 284 285 nlohmann::json& memberArray = 286 asyncResp->res.jsonValue["Members"]; 287 288 std::vector<std::string> subscripIds = 289 EventServiceManager::getInstance().getAllIDs(); 290 memberArray = nlohmann::json::array(); 291 asyncResp->res.jsonValue["Members@odata.count"] = 292 subscripIds.size(); 293 294 for (const std::string& id : subscripIds) 295 { 296 nlohmann::json::object_t member; 297 member["@odata.id"] = boost::urls::format( 298 "/redfish/v1/EventService/Subscriptions/{}" + id); 299 memberArray.emplace_back(std::move(member)); 300 } 301 crow::connections::systemBus->async_method_call( 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