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