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