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