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