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