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