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