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