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