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 77 void doPatch(crow::Response& res, const crow::Request& req, 78 const std::vector<std::string>& params) override 79 { 80 auto asyncResp = std::make_shared<AsyncResp>(res); 81 82 std::optional<bool> serviceEnabled; 83 std::optional<uint32_t> retryAttemps; 84 std::optional<uint32_t> retryInterval; 85 86 if (!json_util::readJson(req, res, "ServiceEnabled", serviceEnabled, 87 "DeliveryRetryAttempts", retryAttemps, 88 "DeliveryRetryIntervalSeconds", retryInterval)) 89 { 90 return; 91 } 92 93 auto [enabled, retryCount, retryTimeoutInterval] = 94 EventServiceManager::getInstance().getEventServiceConfig(); 95 96 if (serviceEnabled) 97 { 98 enabled = *serviceEnabled; 99 } 100 101 if (retryAttemps) 102 { 103 // Supported range [1-3] 104 if ((*retryAttemps < 1) || (*retryAttemps > 3)) 105 { 106 messages::queryParameterOutOfRange( 107 asyncResp->res, std::to_string(*retryAttemps), 108 "DeliveryRetryAttempts", "[1-3]"); 109 } 110 else 111 { 112 retryCount = *retryAttemps; 113 } 114 } 115 116 if (retryInterval) 117 { 118 // Supported range [30 - 180] 119 if ((*retryInterval < 30) || (*retryInterval > 180)) 120 { 121 messages::queryParameterOutOfRange( 122 asyncResp->res, std::to_string(*retryInterval), 123 "DeliveryRetryIntervalSeconds", "[30-180]"); 124 } 125 else 126 { 127 retryTimeoutInterval = *retryInterval; 128 } 129 } 130 131 EventServiceManager::getInstance().setEventServiceConfig( 132 std::make_tuple(enabled, retryCount, retryTimeoutInterval)); 133 } 134 }; 135 136 class SubmitTestEvent : public Node 137 { 138 public: 139 SubmitTestEvent(CrowApp& app) : 140 Node(app, 141 "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/") 142 { 143 entityPrivileges = { 144 {boost::beast::http::verb::get, {{"Login"}}}, 145 {boost::beast::http::verb::head, {{"Login"}}}, 146 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 147 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 148 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 149 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 150 } 151 152 private: 153 void doPost(crow::Response& res, const crow::Request& req, 154 const std::vector<std::string>& params) override 155 { 156 EventServiceManager::getInstance().sendTestEventLog(); 157 res.result(boost::beast::http::status::no_content); 158 res.end(); 159 } 160 }; 161 162 class EventDestinationCollection : public Node 163 { 164 public: 165 EventDestinationCollection(CrowApp& app) : 166 Node(app, "/redfish/v1/EventService/Subscriptions/") 167 { 168 entityPrivileges = { 169 {boost::beast::http::verb::get, {{"Login"}}}, 170 {boost::beast::http::verb::head, {{"Login"}}}, 171 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 172 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 173 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 174 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 175 } 176 177 private: 178 void doGet(crow::Response& res, const crow::Request& req, 179 const std::vector<std::string>& params) override 180 { 181 auto asyncResp = std::make_shared<AsyncResp>(res); 182 183 res.jsonValue = { 184 {"@odata.type", 185 "#EventDestinationCollection.EventDestinationCollection"}, 186 {"@odata.id", "/redfish/v1/EventService/Subscriptions"}, 187 {"Name", "Event Destination Collections"}}; 188 189 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 190 191 std::vector<std::string> subscripIds = 192 EventServiceManager::getInstance().getAllIDs(); 193 memberArray = nlohmann::json::array(); 194 asyncResp->res.jsonValue["Members@odata.count"] = subscripIds.size(); 195 196 for (const std::string& id : subscripIds) 197 { 198 memberArray.push_back( 199 {{"@odata.id", 200 "/redfish/v1/EventService/Subscriptions/" + id}}); 201 } 202 } 203 204 void doPost(crow::Response& res, const crow::Request& req, 205 const std::vector<std::string>& params) override 206 { 207 auto asyncResp = std::make_shared<AsyncResp>(res); 208 209 if (EventServiceManager::getInstance().getNumberOfSubscriptions() >= 210 maxNoOfSubscriptions) 211 { 212 messages::eventSubscriptionLimitExceeded(asyncResp->res); 213 return; 214 } 215 std::string destUrl; 216 std::string protocol; 217 std::optional<std::string> context; 218 std::optional<std::string> subscriptionType; 219 std::optional<std::string> eventFormatType; 220 std::optional<std::string> retryPolicy; 221 std::optional<std::vector<std::string>> msgIds; 222 std::optional<std::vector<std::string>> regPrefixes; 223 std::optional<std::vector<nlohmann::json>> headers; 224 std::optional<std::vector<nlohmann::json>> metricReportDefinitions; 225 226 if (!json_util::readJson( 227 req, res, "Destination", destUrl, "Context", context, 228 "Protocol", protocol, "SubscriptionType", subscriptionType, 229 "EventFormatType", eventFormatType, "HttpHeaders", headers, 230 "RegistryPrefixes", regPrefixes, "MessageIds", msgIds, 231 "DeliveryRetryPolicy", retryPolicy, "MetricReportDefinitions", 232 metricReportDefinitions)) 233 { 234 return; 235 } 236 237 // Validate the URL using regex expression 238 // Format: <protocol>://<host>:<port>/<uri> 239 // protocol: http/https 240 // host: Exclude ' ', ':', '#', '?' 241 // port: Empty or numeric value with ':' seperator. 242 // uri: Start with '/' and Exclude '#', ' ' 243 // Can include query params(ex: '/event?test=1') 244 // TODO: Need to validate hostname extensively(as per rfc) 245 const std::regex urlRegex( 246 "(http|https)://([^/\\x20\\x3f\\x23\\x3a]+):?([0-9]*)(/" 247 "([^\\x20\\x23\\x3f]*\\x3f?([^\\x20\\x23\\x3f])*)?)"); 248 std::cmatch match; 249 if (!std::regex_match(destUrl.c_str(), match, urlRegex)) 250 { 251 messages::propertyValueFormatError(asyncResp->res, destUrl, 252 "Destination"); 253 return; 254 } 255 256 std::string uriProto = std::string(match[1].first, match[1].second); 257 if (uriProto == "http") 258 { 259 #ifndef BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING 260 messages::propertyValueFormatError(asyncResp->res, destUrl, 261 "Destination"); 262 return; 263 #endif 264 } 265 266 std::string host = std::string(match[2].first, match[2].second); 267 std::string port = std::string(match[3].first, match[3].second); 268 std::string path = std::string(match[4].first, match[4].second); 269 if (port.empty()) 270 { 271 if (uriProto == "http") 272 { 273 port = "80"; 274 } 275 else 276 { 277 port = "443"; 278 } 279 } 280 if (path.empty()) 281 { 282 path = "/"; 283 } 284 285 std::shared_ptr<Subscription> subValue = 286 std::make_shared<Subscription>(host, port, path, uriProto); 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 (eventFormatType) 314 { 315 if (std::find(supportedEvtFormatTypes.begin(), 316 supportedEvtFormatTypes.end(), 317 *eventFormatType) == supportedEvtFormatTypes.end()) 318 { 319 messages::propertyValueNotInList( 320 asyncResp->res, *eventFormatType, "EventFormatType"); 321 return; 322 } 323 subValue->eventFormatType = *eventFormatType; 324 } 325 else 326 { 327 // If not specified, use default "Event" 328 subValue->eventFormatType.assign({"Event"}); 329 } 330 331 if (context) 332 { 333 subValue->customText = *context; 334 } 335 336 if (headers) 337 { 338 subValue->httpHeaders = *headers; 339 } 340 341 if (regPrefixes) 342 { 343 for (const std::string& it : *regPrefixes) 344 { 345 if (std::find(supportedRegPrefixes.begin(), 346 supportedRegPrefixes.end(), 347 it) == supportedRegPrefixes.end()) 348 { 349 messages::propertyValueNotInList(asyncResp->res, it, 350 "RegistryPrefixes"); 351 return; 352 } 353 } 354 subValue->registryPrefixes = *regPrefixes; 355 } 356 357 if (msgIds) 358 { 359 // Do we need to loop-up MessageRegistry and validate 360 // data for authenticity??? Not mandate, i believe. 361 subValue->registryMsgIds = *msgIds; 362 } 363 364 if (retryPolicy) 365 { 366 if (std::find(supportedRetryPolicies.begin(), 367 supportedRetryPolicies.end(), 368 *retryPolicy) == supportedRetryPolicies.end()) 369 { 370 messages::propertyValueNotInList(asyncResp->res, *retryPolicy, 371 "DeliveryRetryPolicy"); 372 return; 373 } 374 subValue->retryPolicy = *retryPolicy; 375 } 376 else 377 { 378 // Default "TerminateAfterRetries" 379 subValue->retryPolicy = "TerminateAfterRetries"; 380 } 381 382 if (metricReportDefinitions) 383 { 384 subValue->metricReportDefinitions = *metricReportDefinitions; 385 } 386 387 std::string id = 388 EventServiceManager::getInstance().addSubscription(subValue); 389 if (id.empty()) 390 { 391 messages::internalError(asyncResp->res); 392 return; 393 } 394 395 messages::created(asyncResp->res); 396 asyncResp->res.addHeader( 397 "Location", "/redfish/v1/EventService/Subscriptions/" + id); 398 } 399 }; 400 401 class EventDestination : public Node 402 { 403 public: 404 EventDestination(CrowApp& app) : 405 Node(app, "/redfish/v1/EventService/Subscriptions/<str>/", 406 std::string()) 407 { 408 entityPrivileges = { 409 {boost::beast::http::verb::get, {{"Login"}}}, 410 {boost::beast::http::verb::head, {{"Login"}}}, 411 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 412 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 413 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 414 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 415 } 416 417 private: 418 void doGet(crow::Response& res, const crow::Request& req, 419 const std::vector<std::string>& params) override 420 { 421 auto asyncResp = std::make_shared<AsyncResp>(res); 422 if (params.size() != 1) 423 { 424 messages::internalError(asyncResp->res); 425 return; 426 } 427 428 std::shared_ptr<Subscription> subValue = 429 EventServiceManager::getInstance().getSubscription(params[0]); 430 if (subValue == nullptr) 431 { 432 res.result(boost::beast::http::status::not_found); 433 res.end(); 434 return; 435 } 436 const std::string& id = params[0]; 437 438 res.jsonValue = { 439 {"@odata.type", "#EventDestination.v1_7_0.EventDestination"}, 440 {"Protocol", "Redfish"}}; 441 asyncResp->res.jsonValue["@odata.id"] = 442 "/redfish/v1/EventService/Subscriptions/" + id; 443 asyncResp->res.jsonValue["Id"] = id; 444 asyncResp->res.jsonValue["Name"] = "Event Destination " + id; 445 asyncResp->res.jsonValue["Destination"] = subValue->destinationUrl; 446 asyncResp->res.jsonValue["Context"] = subValue->customText; 447 asyncResp->res.jsonValue["SubscriptionType"] = 448 subValue->subscriptionType; 449 asyncResp->res.jsonValue["HttpHeaders"] = subValue->httpHeaders; 450 asyncResp->res.jsonValue["EventFormatType"] = subValue->eventFormatType; 451 asyncResp->res.jsonValue["RegistryPrefixes"] = 452 subValue->registryPrefixes; 453 asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds; 454 asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy; 455 asyncResp->res.jsonValue["MetricReportDefinitions"] = 456 subValue->metricReportDefinitions; 457 } 458 459 void doPatch(crow::Response& res, const crow::Request& req, 460 const std::vector<std::string>& params) override 461 { 462 auto asyncResp = std::make_shared<AsyncResp>(res); 463 if (params.size() != 1) 464 { 465 messages::internalError(asyncResp->res); 466 return; 467 } 468 469 std::shared_ptr<Subscription> subValue = 470 EventServiceManager::getInstance().getSubscription(params[0]); 471 if (subValue == nullptr) 472 { 473 res.result(boost::beast::http::status::not_found); 474 res.end(); 475 return; 476 } 477 478 std::optional<std::string> context; 479 std::optional<std::string> retryPolicy; 480 std::optional<std::vector<nlohmann::json>> headers; 481 482 if (!json_util::readJson(req, res, "Context", context, 483 "DeliveryRetryPolicy", retryPolicy, 484 "HttpHeaders", headers)) 485 { 486 return; 487 } 488 489 if (context) 490 { 491 subValue->customText = *context; 492 } 493 494 if (headers) 495 { 496 subValue->httpHeaders = *headers; 497 } 498 499 if (retryPolicy) 500 { 501 if (std::find(supportedRetryPolicies.begin(), 502 supportedRetryPolicies.end(), 503 *retryPolicy) == supportedRetryPolicies.end()) 504 { 505 messages::propertyValueNotInList(asyncResp->res, *retryPolicy, 506 "DeliveryRetryPolicy"); 507 return; 508 } 509 subValue->retryPolicy = *retryPolicy; 510 } 511 512 EventServiceManager::getInstance().updateSubscriptionData(); 513 } 514 515 void doDelete(crow::Response& res, const crow::Request& req, 516 const std::vector<std::string>& params) override 517 { 518 auto asyncResp = std::make_shared<AsyncResp>(res); 519 520 if (params.size() != 1) 521 { 522 messages::internalError(asyncResp->res); 523 return; 524 } 525 526 if (!EventServiceManager::getInstance().isSubscriptionExist(params[0])) 527 { 528 res.result(boost::beast::http::status::not_found); 529 res.end(); 530 return; 531 } 532 EventServiceManager::getInstance().deleteSubscription(params[0]); 533 } 534 }; 535 536 } // namespace redfish 537