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