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