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