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 persistent_data::UserSubscription{}, *url, app.ioContext()); 448 449 subValue->userSub.destinationUrl = std::move(*url); 450 451 if (subscriptionType) 452 { 453 if (*subscriptionType != "RedfishEvent") 454 { 455 messages::propertyValueNotInList( 456 asyncResp->res, *subscriptionType, "SubscriptionType"); 457 return; 458 } 459 subValue->userSub.subscriptionType = *subscriptionType; 460 } 461 else 462 { 463 // Default 464 subValue->userSub.subscriptionType = "RedfishEvent"; 465 } 466 467 if (protocol != "Redfish") 468 { 469 messages::propertyValueNotInList(asyncResp->res, protocol, 470 "Protocol"); 471 return; 472 } 473 subValue->userSub.protocol = protocol; 474 475 if (verifyCertificate) 476 { 477 subValue->userSub.verifyCertificate = *verifyCertificate; 478 } 479 480 if (eventFormatType2) 481 { 482 if (std::ranges::find(supportedEvtFormatTypes, 483 *eventFormatType2) == 484 supportedEvtFormatTypes.end()) 485 { 486 messages::propertyValueNotInList( 487 asyncResp->res, *eventFormatType2, "EventFormatType"); 488 return; 489 } 490 subValue->userSub.eventFormatType = *eventFormatType2; 491 } 492 else 493 { 494 // If not specified, use default "Event" 495 subValue->userSub.eventFormatType = "Event"; 496 } 497 498 if (context) 499 { 500 // This value is selected arbitrarily. 501 constexpr const size_t maxContextSize = 256; 502 if (context->size() > maxContextSize) 503 { 504 messages::stringValueTooLong(asyncResp->res, "Context", 505 maxContextSize); 506 return; 507 } 508 subValue->userSub.customText = *context; 509 } 510 511 if (headers) 512 { 513 size_t cumulativeLen = 0; 514 515 for (const nlohmann::json::object_t& headerChunk : *headers) 516 { 517 for (const auto& item : headerChunk) 518 { 519 const std::string* value = 520 item.second.get_ptr<const std::string*>(); 521 if (value == nullptr) 522 { 523 messages::propertyValueFormatError( 524 asyncResp->res, item.second, 525 "HttpHeaders/" + item.first); 526 return; 527 } 528 // Adding a new json value is the size of the key, + 529 // the size of the value + 2 * 2 quotes for each, + 530 // the colon and space between. example: 531 // "key": "value" 532 cumulativeLen += item.first.size() + value->size() + 6; 533 // This value is selected to mirror http_connection.hpp 534 constexpr const uint16_t maxHeaderSizeED = 8096; 535 if (cumulativeLen > maxHeaderSizeED) 536 { 537 messages::arraySizeTooLong( 538 asyncResp->res, "HttpHeaders", maxHeaderSizeED); 539 return; 540 } 541 subValue->userSub.httpHeaders.set(item.first, *value); 542 } 543 } 544 } 545 546 if (regPrefixes) 547 { 548 for (const std::string& it : *regPrefixes) 549 { 550 if (std::ranges::find(supportedRegPrefixes, it) == 551 supportedRegPrefixes.end()) 552 { 553 messages::propertyValueNotInList(asyncResp->res, it, 554 "RegistryPrefixes"); 555 return; 556 } 557 } 558 subValue->userSub.registryPrefixes = *regPrefixes; 559 } 560 561 if (originResources) 562 { 563 subValue->userSub.originResources = *originResources; 564 } 565 566 if (resTypes) 567 { 568 for (const std::string& it : *resTypes) 569 { 570 if (std::ranges::find(supportedResourceTypes, it) == 571 supportedResourceTypes.end()) 572 { 573 messages::propertyValueNotInList(asyncResp->res, it, 574 "ResourceTypes"); 575 return; 576 } 577 } 578 subValue->userSub.resourceTypes = *resTypes; 579 } 580 581 if (msgIds) 582 { 583 std::vector<std::string> registryPrefix; 584 585 // If no registry prefixes are mentioned, consider all 586 // supported prefixes 587 if (subValue->userSub.registryPrefixes.empty()) 588 { 589 registryPrefix.assign(supportedRegPrefixes.begin(), 590 supportedRegPrefixes.end()); 591 } 592 else 593 { 594 registryPrefix = subValue->userSub.registryPrefixes; 595 } 596 597 for (const std::string& id : *msgIds) 598 { 599 bool validId = false; 600 601 // Check for Message ID in each of the selected Registry 602 for (const std::string& it : registryPrefix) 603 { 604 const std::span<const redfish::registries::MessageEntry> 605 registry = 606 redfish::registries::getRegistryFromPrefix(it); 607 608 if (std::ranges::any_of( 609 registry, 610 [&id](const redfish::registries::MessageEntry& 611 messageEntry) { 612 return id == messageEntry.first; 613 })) 614 { 615 validId = true; 616 break; 617 } 618 } 619 620 if (!validId) 621 { 622 messages::propertyValueNotInList(asyncResp->res, id, 623 "MessageIds"); 624 return; 625 } 626 } 627 628 subValue->userSub.registryMsgIds = *msgIds; 629 } 630 631 if (retryPolicy) 632 { 633 if (std::ranges::find(supportedRetryPolicies, *retryPolicy) == 634 supportedRetryPolicies.end()) 635 { 636 messages::propertyValueNotInList( 637 asyncResp->res, *retryPolicy, "DeliveryRetryPolicy"); 638 return; 639 } 640 subValue->userSub.retryPolicy = *retryPolicy; 641 } 642 else 643 { 644 // Default "TerminateAfterRetries" 645 subValue->userSub.retryPolicy = "TerminateAfterRetries"; 646 } 647 648 if (mrdJsonArray) 649 { 650 for (nlohmann::json::object_t& mrdObj : *mrdJsonArray) 651 { 652 std::string mrdUri; 653 654 if (!json_util::readJsonObject(mrdObj, asyncResp->res, 655 "@odata.id", mrdUri)) 656 657 { 658 return; 659 } 660 subValue->userSub.metricReportDefinitions.emplace_back( 661 mrdUri); 662 } 663 } 664 665 std::string id = 666 EventServiceManager::getInstance().addPushSubscription( 667 subValue); 668 if (id.empty()) 669 { 670 messages::internalError(asyncResp->res); 671 return; 672 } 673 674 messages::created(asyncResp->res); 675 asyncResp->res.addHeader( 676 "Location", "/redfish/v1/EventService/Subscriptions/" + id); 677 }); 678 } 679 680 inline void requestRoutesEventDestination(App& app) 681 { 682 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 683 .privileges(redfish::privileges::getEventDestination) 684 .methods(boost::beast::http::verb::get)( 685 [&app](const crow::Request& req, 686 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 687 const std::string& param) { 688 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 689 { 690 return; 691 } 692 693 if (param.starts_with("snmp")) 694 { 695 getSnmpTrapClient(asyncResp, param); 696 return; 697 } 698 699 std::shared_ptr<Subscription> subValue = 700 EventServiceManager::getInstance().getSubscription(param); 701 if (subValue == nullptr) 702 { 703 asyncResp->res.result( 704 boost::beast::http::status::not_found); 705 return; 706 } 707 const std::string& id = param; 708 709 const persistent_data::UserSubscription& userSub = 710 subValue->userSub; 711 712 nlohmann::json& jVal = asyncResp->res.jsonValue; 713 jVal["@odata.type"] = 714 "#EventDestination.v1_14_1.EventDestination"; 715 jVal["Protocol"] = 716 event_destination::EventDestinationProtocol::Redfish; 717 jVal["@odata.id"] = boost::urls::format( 718 "/redfish/v1/EventService/Subscriptions/{}", id); 719 jVal["Id"] = id; 720 jVal["Name"] = "Event Destination " + id; 721 jVal["Destination"] = userSub.destinationUrl; 722 jVal["Context"] = userSub.customText; 723 jVal["SubscriptionType"] = userSub.subscriptionType; 724 jVal["HttpHeaders"] = nlohmann::json::array(); 725 jVal["EventFormatType"] = userSub.eventFormatType; 726 jVal["RegistryPrefixes"] = userSub.registryPrefixes; 727 jVal["ResourceTypes"] = userSub.resourceTypes; 728 729 jVal["MessageIds"] = userSub.registryMsgIds; 730 jVal["DeliveryRetryPolicy"] = userSub.retryPolicy; 731 jVal["VerifyCertificate"] = userSub.verifyCertificate; 732 733 nlohmann::json::array_t mrdJsonArray; 734 for (const auto& mdrUri : userSub.metricReportDefinitions) 735 { 736 nlohmann::json::object_t mdr; 737 mdr["@odata.id"] = mdrUri; 738 mrdJsonArray.emplace_back(std::move(mdr)); 739 } 740 jVal["MetricReportDefinitions"] = mrdJsonArray; 741 }); 742 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 743 // The below privilege is wrong, it should be ConfigureManager OR 744 // ConfigureSelf 745 // https://github.com/openbmc/bmcweb/issues/220 746 //.privileges(redfish::privileges::patchEventDestination) 747 .privileges({{"ConfigureManager"}}) 748 .methods(boost::beast::http::verb::patch)( 749 [&app](const crow::Request& req, 750 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 751 const std::string& param) { 752 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 753 { 754 return; 755 } 756 std::shared_ptr<Subscription> subValue = 757 EventServiceManager::getInstance().getSubscription(param); 758 if (subValue == nullptr) 759 { 760 asyncResp->res.result( 761 boost::beast::http::status::not_found); 762 return; 763 } 764 765 std::optional<std::string> context; 766 std::optional<std::string> retryPolicy; 767 std::optional<bool> verifyCertificate; 768 std::optional<std::vector<nlohmann::json::object_t>> headers; 769 770 if (!json_util::readJsonPatch( // 771 req, asyncResp->res, // 772 "Context", context, // 773 "DeliveryRetryPolicy", retryPolicy, // 774 "HttpHeaders", headers, // 775 "VerifyCertificate", verifyCertificate // 776 )) 777 { 778 return; 779 } 780 781 if (context) 782 { 783 subValue->userSub.customText = *context; 784 } 785 786 if (headers) 787 { 788 boost::beast::http::fields fields; 789 for (const nlohmann::json::object_t& headerChunk : *headers) 790 { 791 for (const auto& it : headerChunk) 792 { 793 const std::string* value = 794 it.second.get_ptr<const std::string*>(); 795 if (value == nullptr) 796 { 797 messages::propertyValueFormatError( 798 asyncResp->res, it.second, 799 "HttpHeaders/" + it.first); 800 return; 801 } 802 fields.set(it.first, *value); 803 } 804 } 805 subValue->userSub.httpHeaders = std::move(fields); 806 } 807 808 if (retryPolicy) 809 { 810 if (std::ranges::find(supportedRetryPolicies, 811 *retryPolicy) == 812 supportedRetryPolicies.end()) 813 { 814 messages::propertyValueNotInList(asyncResp->res, 815 *retryPolicy, 816 "DeliveryRetryPolicy"); 817 return; 818 } 819 subValue->userSub.retryPolicy = *retryPolicy; 820 } 821 822 if (verifyCertificate) 823 { 824 subValue->userSub.verifyCertificate = *verifyCertificate; 825 } 826 827 // Sync Subscription to UserSubscriptionConfig 828 persistent_data::EventServiceStore::getInstance() 829 .updateUserSubscriptionConfig(subValue->userSub); 830 831 EventServiceManager::getInstance().updateSubscriptionData(); 832 }); 833 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 834 // The below privilege is wrong, it should be ConfigureManager OR 835 // ConfigureSelf 836 // https://github.com/openbmc/bmcweb/issues/220 837 //.privileges(redfish::privileges::deleteEventDestination) 838 .privileges({{"ConfigureManager"}}) 839 .methods(boost::beast::http::verb::delete_)( 840 [&app](const crow::Request& req, 841 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 842 const std::string& param) { 843 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 844 { 845 return; 846 } 847 EventServiceManager& event = EventServiceManager::getInstance(); 848 if (param.starts_with("snmp")) 849 { 850 deleteSnmpTrapClient(asyncResp, param); 851 event.deleteSubscription(param); 852 return; 853 } 854 855 if (!event.deleteSubscription(param)) 856 { 857 messages::resourceNotFound(asyncResp->res, 858 "EventDestination", param); 859 return; 860 } 861 messages::success(asyncResp->res); 862 }); 863 } 864 865 } // namespace redfish 866