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*, 3> supportedRegPrefixes = { 49 "Base", "OpenBMC", "TaskEvent"}; 50 static constexpr const std::array<const char*, 3> supportedRetryPolicies = { 51 "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; 52 53 static constexpr const std::array<const char*, 1> supportedResourceTypes = { 54 "Task"}; 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 } 713 714 inline void requestRoutesEventDestination(App& app) 715 { 716 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 717 .privileges(redfish::privileges::getEventDestination) 718 .methods(boost::beast::http::verb::get)( 719 [&app](const crow::Request& req, 720 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 721 const std::string& param) { 722 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 723 { 724 return; 725 } 726 727 if (param.starts_with("snmp")) 728 { 729 getSnmpTrapClient(asyncResp, param); 730 return; 731 } 732 733 std::shared_ptr<Subscription> subValue = 734 EventServiceManager::getInstance().getSubscription(param); 735 if (subValue == nullptr) 736 { 737 asyncResp->res.result( 738 boost::beast::http::status::not_found); 739 return; 740 } 741 const std::string& id = param; 742 743 const persistent_data::UserSubscription& userSub = 744 *subValue->userSub; 745 746 nlohmann::json& jVal = asyncResp->res.jsonValue; 747 jVal["@odata.type"] = 748 "#EventDestination.v1_14_1.EventDestination"; 749 jVal["Protocol"] = 750 event_destination::EventDestinationProtocol::Redfish; 751 jVal["@odata.id"] = boost::urls::format( 752 "/redfish/v1/EventService/Subscriptions/{}", id); 753 jVal["Id"] = id; 754 jVal["Name"] = "Event Destination " + id; 755 jVal["Destination"] = userSub.destinationUrl; 756 jVal["Context"] = userSub.customText; 757 jVal["SubscriptionType"] = userSub.subscriptionType; 758 jVal["HttpHeaders"] = nlohmann::json::array(); 759 jVal["EventFormatType"] = userSub.eventFormatType; 760 jVal["RegistryPrefixes"] = userSub.registryPrefixes; 761 jVal["ResourceTypes"] = userSub.resourceTypes; 762 763 jVal["MessageIds"] = userSub.registryMsgIds; 764 jVal["DeliveryRetryPolicy"] = userSub.retryPolicy; 765 jVal["SendHeartbeat"] = userSub.sendHeartbeat; 766 jVal["HeartbeatIntervalMinutes"] = userSub.hbIntervalMinutes; 767 jVal["VerifyCertificate"] = userSub.verifyCertificate; 768 769 nlohmann::json::array_t mrdJsonArray; 770 for (const auto& mdrUri : userSub.metricReportDefinitions) 771 { 772 nlohmann::json::object_t mdr; 773 mdr["@odata.id"] = mdrUri; 774 mrdJsonArray.emplace_back(std::move(mdr)); 775 } 776 jVal["MetricReportDefinitions"] = mrdJsonArray; 777 }); 778 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 779 // The below privilege is wrong, it should be ConfigureManager OR 780 // ConfigureSelf 781 // https://github.com/openbmc/bmcweb/issues/220 782 //.privileges(redfish::privileges::patchEventDestination) 783 .privileges({{"ConfigureManager"}}) 784 .methods(boost::beast::http::verb::patch)( 785 [&app](const crow::Request& req, 786 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 787 const std::string& param) { 788 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 789 { 790 return; 791 } 792 std::shared_ptr<Subscription> subValue = 793 EventServiceManager::getInstance().getSubscription(param); 794 if (subValue == nullptr) 795 { 796 asyncResp->res.result( 797 boost::beast::http::status::not_found); 798 return; 799 } 800 801 std::optional<std::string> context; 802 std::optional<std::string> retryPolicy; 803 std::optional<bool> sendHeartbeat; 804 std::optional<uint64_t> hbIntervalMinutes; 805 std::optional<bool> verifyCertificate; 806 std::optional<std::vector<nlohmann::json::object_t>> headers; 807 808 if (!json_util::readJsonPatch( // 809 req, asyncResp->res, // 810 "Context", context, // 811 "DeliveryRetryPolicy", retryPolicy, // 812 "HeartbeatIntervalMinutes", hbIntervalMinutes, // 813 "HttpHeaders", headers, // 814 "SendHeartbeat", sendHeartbeat, // 815 "VerifyCertificate", verifyCertificate // 816 )) 817 { 818 return; 819 } 820 821 if (context) 822 { 823 subValue->userSub->customText = *context; 824 } 825 826 if (headers) 827 { 828 boost::beast::http::fields fields; 829 for (const nlohmann::json::object_t& headerChunk : *headers) 830 { 831 for (const auto& it : headerChunk) 832 { 833 const std::string* value = 834 it.second.get_ptr<const std::string*>(); 835 if (value == nullptr) 836 { 837 messages::propertyValueFormatError( 838 asyncResp->res, it.second, 839 "HttpHeaders/" + it.first); 840 return; 841 } 842 fields.set(it.first, *value); 843 } 844 } 845 subValue->userSub->httpHeaders = std::move(fields); 846 } 847 848 if (retryPolicy) 849 { 850 if (std::ranges::find(supportedRetryPolicies, 851 *retryPolicy) == 852 supportedRetryPolicies.end()) 853 { 854 messages::propertyValueNotInList(asyncResp->res, 855 *retryPolicy, 856 "DeliveryRetryPolicy"); 857 return; 858 } 859 subValue->userSub->retryPolicy = *retryPolicy; 860 } 861 862 if (sendHeartbeat) 863 { 864 subValue->userSub->sendHeartbeat = *sendHeartbeat; 865 } 866 if (hbIntervalMinutes) 867 { 868 if (*hbIntervalMinutes < 1 || *hbIntervalMinutes > 65535) 869 { 870 messages::propertyValueOutOfRange( 871 asyncResp->res, *hbIntervalMinutes, 872 "HeartbeatIntervalMinutes"); 873 return; 874 } 875 subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes; 876 } 877 878 if (verifyCertificate) 879 { 880 subValue->userSub->verifyCertificate = *verifyCertificate; 881 } 882 883 EventServiceManager::getInstance().updateSubscriptionData(); 884 }); 885 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 886 // The below privilege is wrong, it should be ConfigureManager OR 887 // ConfigureSelf 888 // https://github.com/openbmc/bmcweb/issues/220 889 //.privileges(redfish::privileges::deleteEventDestination) 890 .privileges({{"ConfigureManager"}}) 891 .methods(boost::beast::http::verb::delete_)( 892 [&app](const crow::Request& req, 893 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 894 const std::string& param) { 895 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 896 { 897 return; 898 } 899 EventServiceManager& event = EventServiceManager::getInstance(); 900 if (param.starts_with("snmp")) 901 { 902 deleteSnmpTrapClient(asyncResp, param); 903 event.deleteSubscription(param); 904 return; 905 } 906 907 if (!event.deleteSubscription(param)) 908 { 909 messages::resourceNotFound(asyncResp->res, 910 "EventDestination", param); 911 return; 912 } 913 messages::success(asyncResp->res); 914 }); 915 } 916 917 } // namespace redfish 918