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 "event_service_manager.hpp" 18 19 #include <app.hpp> 20 #include <boost/beast/http/fields.hpp> 21 #include <http/utility.hpp> 22 #include <logging.hpp> 23 #include <query.hpp> 24 #include <registries/privilege_registry.hpp> 25 26 #include <span> 27 28 namespace redfish 29 { 30 31 static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = { 32 eventFormatType, metricReportFormatType}; 33 static constexpr const std::array<const char*, 3> supportedRegPrefixes = { 34 "Base", "OpenBMC", "TaskEvent"}; 35 static constexpr const std::array<const char*, 3> supportedRetryPolicies = { 36 "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; 37 38 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 39 static constexpr const std::array<const char*, 2> supportedResourceTypes = { 40 "IBMConfigFile", "Task"}; 41 #else 42 static constexpr const std::array<const char*, 1> supportedResourceTypes = { 43 "Task"}; 44 #endif 45 46 static constexpr const uint8_t maxNoOfSubscriptions = 20; 47 48 inline void requestRoutesEventService(App& app) 49 { 50 BMCWEB_ROUTE(app, "/redfish/v1/EventService/") 51 .privileges(redfish::privileges::getEventService) 52 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req, 53 const std::shared_ptr< 54 bmcweb::AsyncResp>& 55 asyncResp) { 56 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 57 { 58 return; 59 } 60 61 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService"; 62 asyncResp->res.jsonValue["@odata.type"] = 63 "#EventService.v1_5_0.EventService"; 64 asyncResp->res.jsonValue["Id"] = "EventService"; 65 asyncResp->res.jsonValue["Name"] = "Event Service"; 66 asyncResp->res.jsonValue["Subscriptions"]["@odata.id"] = 67 "/redfish/v1/EventService/Subscriptions"; 68 asyncResp->res.jsonValue["Actions"]["#EventService.SubmitTestEvent"] 69 ["target"] = 70 "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent"; 71 72 const persistent_data::EventServiceConfig eventServiceConfig = 73 persistent_data::EventServiceStore::getInstance() 74 .getEventServiceConfig(); 75 76 asyncResp->res.jsonValue["Status"]["State"] = 77 (eventServiceConfig.enabled ? "Enabled" : "Disabled"); 78 asyncResp->res.jsonValue["ServiceEnabled"] = 79 eventServiceConfig.enabled; 80 asyncResp->res.jsonValue["DeliveryRetryAttempts"] = 81 eventServiceConfig.retryAttempts; 82 asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] = 83 eventServiceConfig.retryTimeoutInterval; 84 asyncResp->res.jsonValue["EventFormatTypes"] = 85 supportedEvtFormatTypes; 86 asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes; 87 asyncResp->res.jsonValue["ResourceTypes"] = supportedResourceTypes; 88 89 nlohmann::json supportedSSEFilters = { 90 {"EventFormatType", true}, {"MessageId", true}, 91 {"MetricReportDefinition", true}, {"RegistryPrefix", true}, 92 {"OriginResource", false}, {"ResourceType", false}}; 93 94 asyncResp->res.jsonValue["SSEFilterPropertiesSupported"] = 95 supportedSSEFilters; 96 }); 97 98 BMCWEB_ROUTE(app, "/redfish/v1/EventService/") 99 .privileges(redfish::privileges::patchEventService) 100 .methods(boost::beast::http::verb::patch)( 101 [&app](const crow::Request& req, 102 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 103 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 104 { 105 return; 106 } 107 std::optional<bool> serviceEnabled; 108 std::optional<uint32_t> retryAttemps; 109 std::optional<uint32_t> retryInterval; 110 111 if (!json_util::readJsonPatch( 112 req, asyncResp->res, "ServiceEnabled", serviceEnabled, 113 "DeliveryRetryAttempts", retryAttemps, 114 "DeliveryRetryIntervalSeconds", retryInterval)) 115 { 116 return; 117 } 118 119 persistent_data::EventServiceConfig eventServiceConfig = 120 persistent_data::EventServiceStore::getInstance() 121 .getEventServiceConfig(); 122 123 if (serviceEnabled) 124 { 125 eventServiceConfig.enabled = *serviceEnabled; 126 } 127 128 if (retryAttemps) 129 { 130 // Supported range [1-3] 131 if ((*retryAttemps < 1) || (*retryAttemps > 3)) 132 { 133 messages::queryParameterOutOfRange( 134 asyncResp->res, std::to_string(*retryAttemps), 135 "DeliveryRetryAttempts", "[1-3]"); 136 } 137 else 138 { 139 eventServiceConfig.retryAttempts = *retryAttemps; 140 } 141 } 142 143 if (retryInterval) 144 { 145 // Supported range [30 - 180] 146 if ((*retryInterval < 30) || (*retryInterval > 180)) 147 { 148 messages::queryParameterOutOfRange( 149 asyncResp->res, std::to_string(*retryInterval), 150 "DeliveryRetryIntervalSeconds", "[30-180]"); 151 } 152 else 153 { 154 eventServiceConfig.retryTimeoutInterval = 155 *retryInterval; 156 } 157 } 158 159 EventServiceManager::getInstance().setEventServiceConfig( 160 eventServiceConfig); 161 }); 162 } 163 164 inline void requestRoutesSubmitTestEvent(App& app) 165 { 166 167 BMCWEB_ROUTE( 168 app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/") 169 .privileges(redfish::privileges::postEventService) 170 .methods(boost::beast::http::verb::post)( 171 [&app](const crow::Request& req, 172 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 173 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 174 { 175 return; 176 } 177 if (!EventServiceManager::getInstance().sendTestEventLog()) 178 { 179 messages::serviceDisabled(asyncResp->res, 180 "/redfish/v1/EventService/"); 181 return; 182 } 183 asyncResp->res.result(boost::beast::http::status::no_content); 184 }); 185 } 186 187 inline void requestRoutesEventDestinationCollection(App& app) 188 { 189 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") 190 .privileges(redfish::privileges::getEventDestinationCollection) 191 .methods(boost::beast::http::verb::get)( 192 [&app](const crow::Request& req, 193 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 194 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 195 { 196 return; 197 } 198 asyncResp->res.jsonValue["@odata.type"] = 199 "#EventDestinationCollection.EventDestinationCollection"; 200 asyncResp->res.jsonValue["@odata.id"] = 201 "/redfish/v1/EventService/Subscriptions"; 202 asyncResp->res.jsonValue["Name"] = 203 "Event Destination Collections"; 204 205 nlohmann::json& memberArray = 206 asyncResp->res.jsonValue["Members"]; 207 208 std::vector<std::string> subscripIds = 209 EventServiceManager::getInstance().getAllIDs(); 210 memberArray = nlohmann::json::array(); 211 asyncResp->res.jsonValue["Members@odata.count"] = 212 subscripIds.size(); 213 214 for (const std::string& id : subscripIds) 215 { 216 nlohmann::json::object_t member; 217 member["@odata.id"] = 218 "/redfish/v1/EventService/Subscriptions/" + id; 219 memberArray.push_back(std::move(member)); 220 } 221 }); 222 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") 223 .privileges(redfish::privileges::postEventDestinationCollection) 224 .methods( 225 boost::beast::http::verb:: 226 post)([&app]( 227 const crow::Request& req, 228 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 229 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 230 { 231 return; 232 } 233 if (EventServiceManager::getInstance().getNumberOfSubscriptions() >= 234 maxNoOfSubscriptions) 235 { 236 messages::eventSubscriptionLimitExceeded(asyncResp->res); 237 return; 238 } 239 std::string destUrl; 240 std::string protocol; 241 std::optional<std::string> context; 242 std::optional<std::string> subscriptionType; 243 std::optional<std::string> eventFormatType2; 244 std::optional<std::string> retryPolicy; 245 std::optional<std::vector<std::string>> msgIds; 246 std::optional<std::vector<std::string>> regPrefixes; 247 std::optional<std::vector<std::string>> resTypes; 248 std::optional<std::vector<nlohmann::json>> headers; 249 std::optional<std::vector<nlohmann::json>> mrdJsonArray; 250 251 if (!json_util::readJsonPatch( 252 req, asyncResp->res, "Destination", destUrl, "Context", 253 context, "Protocol", protocol, "SubscriptionType", 254 subscriptionType, "EventFormatType", eventFormatType2, 255 "HttpHeaders", headers, "RegistryPrefixes", regPrefixes, 256 "MessageIds", msgIds, "DeliveryRetryPolicy", retryPolicy, 257 "MetricReportDefinitions", mrdJsonArray, "ResourceTypes", 258 resTypes)) 259 { 260 return; 261 } 262 263 if (regPrefixes && msgIds) 264 { 265 if (!regPrefixes->empty() && !msgIds->empty()) 266 { 267 messages::propertyValueConflict( 268 asyncResp->res, "MessageIds", "RegistryPrefixes"); 269 return; 270 } 271 } 272 273 std::string host; 274 std::string urlProto; 275 uint16_t port = 0; 276 std::string path; 277 278 if (!crow::utility::validateAndSplitUrl(destUrl, urlProto, host, 279 port, path)) 280 { 281 BMCWEB_LOG_WARNING 282 << "Failed to validate and split destination url"; 283 messages::propertyValueFormatError(asyncResp->res, destUrl, 284 "Destination"); 285 return; 286 } 287 288 if (path.empty()) 289 { 290 path = "/"; 291 } 292 std::shared_ptr<Subscription> subValue = 293 std::make_shared<Subscription>(host, port, path, urlProto); 294 295 subValue->destinationUrl = destUrl; 296 297 if (subscriptionType) 298 { 299 if (*subscriptionType != "RedfishEvent") 300 { 301 messages::propertyValueNotInList( 302 asyncResp->res, *subscriptionType, "SubscriptionType"); 303 return; 304 } 305 subValue->subscriptionType = *subscriptionType; 306 } 307 else 308 { 309 subValue->subscriptionType = "RedfishEvent"; // Default 310 } 311 312 if (protocol != "Redfish") 313 { 314 messages::propertyValueNotInList(asyncResp->res, protocol, 315 "Protocol"); 316 return; 317 } 318 subValue->protocol = protocol; 319 320 if (eventFormatType2) 321 { 322 if (std::find(supportedEvtFormatTypes.begin(), 323 supportedEvtFormatTypes.end(), 324 *eventFormatType2) == 325 supportedEvtFormatTypes.end()) 326 { 327 messages::propertyValueNotInList( 328 asyncResp->res, *eventFormatType2, "EventFormatType"); 329 return; 330 } 331 subValue->eventFormatType = *eventFormatType2; 332 } 333 else 334 { 335 // If not specified, use default "Event" 336 subValue->eventFormatType = "Event"; 337 } 338 339 if (context) 340 { 341 subValue->customText = *context; 342 } 343 344 if (headers) 345 { 346 for (const nlohmann::json& headerChunk : *headers) 347 { 348 for (const auto& item : headerChunk.items()) 349 { 350 const std::string* value = 351 item.value().get_ptr<const std::string*>(); 352 if (value == nullptr) 353 { 354 messages::propertyValueFormatError( 355 asyncResp->res, item.value().dump(2, 1), 356 "HttpHeaders/" + item.key()); 357 return; 358 } 359 subValue->httpHeaders.set(item.key(), *value); 360 } 361 } 362 } 363 364 if (regPrefixes) 365 { 366 for (const std::string& it : *regPrefixes) 367 { 368 if (std::find(supportedRegPrefixes.begin(), 369 supportedRegPrefixes.end(), 370 it) == supportedRegPrefixes.end()) 371 { 372 messages::propertyValueNotInList(asyncResp->res, it, 373 "RegistryPrefixes"); 374 return; 375 } 376 } 377 subValue->registryPrefixes = *regPrefixes; 378 } 379 380 if (resTypes) 381 { 382 for (const std::string& it : *resTypes) 383 { 384 if (std::find(supportedResourceTypes.begin(), 385 supportedResourceTypes.end(), 386 it) == supportedResourceTypes.end()) 387 { 388 messages::propertyValueNotInList(asyncResp->res, it, 389 "ResourceTypes"); 390 return; 391 } 392 } 393 subValue->resourceTypes = *resTypes; 394 } 395 396 if (msgIds) 397 { 398 std::vector<std::string> registryPrefix; 399 400 // If no registry prefixes are mentioned, consider all 401 // supported prefixes 402 if (subValue->registryPrefixes.empty()) 403 { 404 registryPrefix.assign(supportedRegPrefixes.begin(), 405 supportedRegPrefixes.end()); 406 } 407 else 408 { 409 registryPrefix = subValue->registryPrefixes; 410 } 411 412 for (const std::string& id : *msgIds) 413 { 414 bool validId = false; 415 416 // Check for Message ID in each of the selected Registry 417 for (const std::string& it : registryPrefix) 418 { 419 const std::span<const redfish::registries::MessageEntry> 420 registry = 421 redfish::registries::getRegistryFromPrefix(it); 422 423 if (std::any_of( 424 registry.begin(), registry.end(), 425 [&id](const redfish::registries::MessageEntry& 426 messageEntry) { 427 return id == messageEntry.first; 428 })) 429 { 430 validId = true; 431 break; 432 } 433 } 434 435 if (!validId) 436 { 437 messages::propertyValueNotInList(asyncResp->res, id, 438 "MessageIds"); 439 return; 440 } 441 } 442 443 subValue->registryMsgIds = *msgIds; 444 } 445 446 if (retryPolicy) 447 { 448 if (std::find(supportedRetryPolicies.begin(), 449 supportedRetryPolicies.end(), 450 *retryPolicy) == supportedRetryPolicies.end()) 451 { 452 messages::propertyValueNotInList( 453 asyncResp->res, *retryPolicy, "DeliveryRetryPolicy"); 454 return; 455 } 456 subValue->retryPolicy = *retryPolicy; 457 } 458 else 459 { 460 // Default "TerminateAfterRetries" 461 subValue->retryPolicy = "TerminateAfterRetries"; 462 } 463 464 if (mrdJsonArray) 465 { 466 for (nlohmann::json& mrdObj : *mrdJsonArray) 467 { 468 std::string mrdUri; 469 470 if (!json_util::readJson(mrdObj, asyncResp->res, 471 "@odata.id", mrdUri)) 472 473 { 474 return; 475 } 476 subValue->metricReportDefinitions.emplace_back(mrdUri); 477 } 478 } 479 480 std::string id = 481 EventServiceManager::getInstance().addSubscription(subValue); 482 if (id.empty()) 483 { 484 messages::internalError(asyncResp->res); 485 return; 486 } 487 488 messages::created(asyncResp->res); 489 asyncResp->res.addHeader( 490 "Location", "/redfish/v1/EventService/Subscriptions/" + id); 491 }); 492 } 493 494 inline void requestRoutesEventDestination(App& app) 495 { 496 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 497 .privileges(redfish::privileges::getEventDestination) 498 .methods(boost::beast::http::verb::get)( 499 [&app](const crow::Request& req, 500 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 501 const std::string& param) { 502 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 503 { 504 return; 505 } 506 std::shared_ptr<Subscription> subValue = 507 EventServiceManager::getInstance().getSubscription(param); 508 if (subValue == nullptr) 509 { 510 asyncResp->res.result( 511 boost::beast::http::status::not_found); 512 return; 513 } 514 const std::string& id = param; 515 516 asyncResp->res.jsonValue["@odata.type"] = 517 "#EventDestination.v1_7_0.EventDestination"; 518 asyncResp->res.jsonValue["Protocol"] = "Redfish"; 519 asyncResp->res.jsonValue["@odata.id"] = 520 "/redfish/v1/EventService/Subscriptions/" + id; 521 asyncResp->res.jsonValue["Id"] = id; 522 asyncResp->res.jsonValue["Name"] = "Event Destination " + id; 523 asyncResp->res.jsonValue["Destination"] = 524 subValue->destinationUrl; 525 asyncResp->res.jsonValue["Context"] = subValue->customText; 526 asyncResp->res.jsonValue["SubscriptionType"] = 527 subValue->subscriptionType; 528 asyncResp->res.jsonValue["HttpHeaders"] = 529 nlohmann::json::array(); 530 asyncResp->res.jsonValue["EventFormatType"] = 531 subValue->eventFormatType; 532 asyncResp->res.jsonValue["RegistryPrefixes"] = 533 subValue->registryPrefixes; 534 asyncResp->res.jsonValue["ResourceTypes"] = 535 subValue->resourceTypes; 536 537 asyncResp->res.jsonValue["MessageIds"] = 538 subValue->registryMsgIds; 539 asyncResp->res.jsonValue["DeliveryRetryPolicy"] = 540 subValue->retryPolicy; 541 542 nlohmann::json::array_t mrdJsonArray; 543 for (const auto& mdrUri : subValue->metricReportDefinitions) 544 { 545 nlohmann::json::object_t mdr; 546 mdr["@odata.id"] = mdrUri; 547 mrdJsonArray.emplace_back(std::move(mdr)); 548 } 549 asyncResp->res.jsonValue["MetricReportDefinitions"] = 550 mrdJsonArray; 551 }); 552 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 553 // The below privilege is wrong, it should be ConfigureManager OR 554 // ConfigureSelf 555 // https://github.com/openbmc/bmcweb/issues/220 556 //.privileges(redfish::privileges::patchEventDestination) 557 .privileges({{"ConfigureManager"}}) 558 .methods(boost::beast::http::verb::patch)( 559 [&app](const crow::Request& req, 560 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 561 const std::string& param) { 562 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 563 { 564 return; 565 } 566 std::shared_ptr<Subscription> subValue = 567 EventServiceManager::getInstance().getSubscription(param); 568 if (subValue == nullptr) 569 { 570 asyncResp->res.result( 571 boost::beast::http::status::not_found); 572 return; 573 } 574 575 std::optional<std::string> context; 576 std::optional<std::string> retryPolicy; 577 std::optional<std::vector<nlohmann::json>> headers; 578 579 if (!json_util::readJsonPatch(req, asyncResp->res, "Context", 580 context, "DeliveryRetryPolicy", 581 retryPolicy, "HttpHeaders", 582 headers)) 583 { 584 return; 585 } 586 587 if (context) 588 { 589 subValue->customText = *context; 590 } 591 592 if (headers) 593 { 594 boost::beast::http::fields fields; 595 for (const nlohmann::json& headerChunk : *headers) 596 { 597 for (auto& it : headerChunk.items()) 598 { 599 const std::string* value = 600 it.value().get_ptr<const std::string*>(); 601 if (value == nullptr) 602 { 603 messages::propertyValueFormatError( 604 asyncResp->res, 605 it.value().dump(2, ' ', true), 606 "HttpHeaders/" + it.key()); 607 return; 608 } 609 fields.set(it.key(), *value); 610 } 611 } 612 subValue->httpHeaders = fields; 613 } 614 615 if (retryPolicy) 616 { 617 if (std::find(supportedRetryPolicies.begin(), 618 supportedRetryPolicies.end(), 619 *retryPolicy) == supportedRetryPolicies.end()) 620 { 621 messages::propertyValueNotInList(asyncResp->res, 622 *retryPolicy, 623 "DeliveryRetryPolicy"); 624 return; 625 } 626 subValue->retryPolicy = *retryPolicy; 627 subValue->updateRetryPolicy(); 628 } 629 630 EventServiceManager::getInstance().updateSubscriptionData(); 631 }); 632 BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") 633 // The below privilege is wrong, it should be ConfigureManager OR 634 // ConfigureSelf 635 // https://github.com/openbmc/bmcweb/issues/220 636 //.privileges(redfish::privileges::deleteEventDestination) 637 .privileges({{"ConfigureManager"}}) 638 .methods(boost::beast::http::verb::delete_)( 639 [&app](const crow::Request& req, 640 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 641 const std::string& param) { 642 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 643 { 644 return; 645 } 646 if (!EventServiceManager::getInstance().isSubscriptionExist( 647 param)) 648 { 649 asyncResp->res.result( 650 boost::beast::http::status::not_found); 651 return; 652 } 653 EventServiceManager::getInstance().deleteSubscription(param); 654 }); 655 } 656 657 } // namespace redfish 658