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