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