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         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         std::optional<std::vector<nlohmann::json>> metricReportDefinitions;
221 
222         if (!json_util::readJson(
223                 req, res, "Destination", destUrl, "Context", context,
224                 "Protocol", protocol, "SubscriptionType", subscriptionType,
225                 "EventFormatType", eventFormatType, "HttpHeaders", headers,
226                 "RegistryPrefixes", regPrefixes, "MessageIds", msgIds,
227                 "DeliveryRetryPolicy", retryPolicy, "MetricReportDefinitions",
228                 metricReportDefinitions))
229         {
230             return;
231         }
232 
233         // Validate the URL using regex expression
234         // Format: <protocol>://<host>:<port>/<uri>
235         // protocol: http/https
236         // host: Exclude ' ', ':', '#', '?'
237         // port: Empty or numeric value with ':' seperator.
238         // uri: Start with '/' and Exclude '#', ' '
239         //      Can include query params(ex: '/event?test=1')
240         // TODO: Need to validate hostname extensively(as per rfc)
241         const std::regex urlRegex(
242             "(http|https)://([^/\\x20\\x3f\\x23\\x3a]+):?([0-9]*)(/"
243             "([^\\x20\\x23\\x3f]*\\x3f?([^\\x20\\x23\\x3f])*)?)");
244         std::cmatch match;
245         if (!std::regex_match(destUrl.c_str(), match, urlRegex))
246         {
247             messages::propertyValueFormatError(asyncResp->res, destUrl,
248                                                "Destination");
249             return;
250         }
251 
252         std::string uriProto = std::string(match[1].first, match[1].second);
253         if (uriProto == "http")
254         {
255 #ifndef BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING
256             messages::propertyValueFormatError(asyncResp->res, destUrl,
257                                                "Destination");
258             return;
259 #endif
260         }
261 
262         std::string host = std::string(match[2].first, match[2].second);
263         std::string port = std::string(match[3].first, match[3].second);
264         std::string path = std::string(match[4].first, match[4].second);
265         if (port.empty())
266         {
267             if (uriProto == "http")
268             {
269                 port = "80";
270             }
271             else
272             {
273                 port = "443";
274             }
275         }
276         if (path.empty())
277         {
278             path = "/";
279         }
280 
281         std::shared_ptr<Subscription> subValue =
282             std::make_shared<Subscription>(host, port, path, uriProto);
283 
284         subValue->destinationUrl = destUrl;
285 
286         if (subscriptionType)
287         {
288             if (*subscriptionType != "RedfishEvent")
289             {
290                 messages::propertyValueNotInList(
291                     asyncResp->res, *subscriptionType, "SubscriptionType");
292                 return;
293             }
294             subValue->subscriptionType = *subscriptionType;
295         }
296         else
297         {
298             subValue->subscriptionType = "RedfishEvent"; // Default
299         }
300 
301         if (protocol != "Redfish")
302         {
303             messages::propertyValueNotInList(asyncResp->res, protocol,
304                                              "Protocol");
305             return;
306         }
307         subValue->protocol = protocol;
308 
309         if (eventFormatType)
310         {
311             if (std::find(supportedEvtFormatTypes.begin(),
312                           supportedEvtFormatTypes.end(),
313                           *eventFormatType) == supportedEvtFormatTypes.end())
314             {
315                 messages::propertyValueNotInList(
316                     asyncResp->res, *eventFormatType, "EventFormatType");
317                 return;
318             }
319             subValue->eventFormatType = *eventFormatType;
320         }
321         else
322         {
323             // If not specified, use default "Event"
324             subValue->eventFormatType.assign({"Event"});
325         }
326 
327         if (context)
328         {
329             subValue->customText = *context;
330         }
331 
332         if (headers)
333         {
334             subValue->httpHeaders = *headers;
335         }
336 
337         if (regPrefixes)
338         {
339             for (const std::string& it : *regPrefixes)
340             {
341                 if (std::find(supportedRegPrefixes.begin(),
342                               supportedRegPrefixes.end(),
343                               it) == supportedRegPrefixes.end())
344                 {
345                     messages::propertyValueNotInList(asyncResp->res, it,
346                                                      "RegistryPrefixes");
347                     return;
348                 }
349             }
350             subValue->registryPrefixes = *regPrefixes;
351         }
352 
353         if (msgIds)
354         {
355             // Do we need to loop-up MessageRegistry and validate
356             // data for authenticity??? Not mandate, i believe.
357             subValue->registryMsgIds = *msgIds;
358         }
359 
360         if (retryPolicy)
361         {
362             if (std::find(supportedRetryPolicies.begin(),
363                           supportedRetryPolicies.end(),
364                           *retryPolicy) == supportedRetryPolicies.end())
365             {
366                 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
367                                                  "DeliveryRetryPolicy");
368                 return;
369             }
370             subValue->retryPolicy = *retryPolicy;
371         }
372         else
373         {
374             // Default "TerminateAfterRetries"
375             subValue->retryPolicy = "TerminateAfterRetries";
376         }
377 
378         if (metricReportDefinitions)
379         {
380             subValue->metricReportDefinitions = *metricReportDefinitions;
381         }
382 
383         std::string id =
384             EventServiceManager::getInstance().addSubscription(subValue);
385         if (id.empty())
386         {
387             messages::internalError(asyncResp->res);
388             return;
389         }
390 
391         messages::created(asyncResp->res);
392         asyncResp->res.addHeader(
393             "Location", "/redfish/v1/EventService/Subscriptions/" + id);
394     }
395 };
396 
397 class EventDestination : public Node
398 {
399   public:
400     EventDestination(CrowApp& app) :
401         Node(app, "/redfish/v1/EventService/Subscriptions/<str>/",
402              std::string())
403     {
404         entityPrivileges = {
405             {boost::beast::http::verb::get, {{"Login"}}},
406             {boost::beast::http::verb::head, {{"Login"}}},
407             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
408             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
409             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
410             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
411     }
412 
413   private:
414     void doGet(crow::Response& res, const crow::Request& req,
415                const std::vector<std::string>& params) override
416     {
417         auto asyncResp = std::make_shared<AsyncResp>(res);
418         if (params.size() != 1)
419         {
420             messages::internalError(asyncResp->res);
421             return;
422         }
423 
424         std::shared_ptr<Subscription> subValue =
425             EventServiceManager::getInstance().getSubscription(params[0]);
426         if (subValue == nullptr)
427         {
428             res.result(boost::beast::http::status::not_found);
429             res.end();
430             return;
431         }
432         const std::string& id = params[0];
433 
434         res.jsonValue = {
435             {"@odata.type", "#EventDestination.v1_7_0.EventDestination"},
436             {"Protocol", "Redfish"}};
437         asyncResp->res.jsonValue["@odata.id"] =
438             "/redfish/v1/EventService/Subscriptions/" + id;
439         asyncResp->res.jsonValue["Id"] = id;
440         asyncResp->res.jsonValue["Name"] = "Event Destination " + id;
441         asyncResp->res.jsonValue["Destination"] = subValue->destinationUrl;
442         asyncResp->res.jsonValue["Context"] = subValue->customText;
443         asyncResp->res.jsonValue["SubscriptionType"] =
444             subValue->subscriptionType;
445         asyncResp->res.jsonValue["HttpHeaders"] = subValue->httpHeaders;
446         asyncResp->res.jsonValue["EventFormatType"] = subValue->eventFormatType;
447         asyncResp->res.jsonValue["RegistryPrefixes"] =
448             subValue->registryPrefixes;
449         asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds;
450         asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy;
451         asyncResp->res.jsonValue["MetricReportDefinitions"] =
452             subValue->metricReportDefinitions;
453     }
454 
455     void doPatch(crow::Response& res, const crow::Request& req,
456                  const std::vector<std::string>& params) override
457     {
458         auto asyncResp = std::make_shared<AsyncResp>(res);
459         if (params.size() != 1)
460         {
461             messages::internalError(asyncResp->res);
462             return;
463         }
464 
465         std::shared_ptr<Subscription> subValue =
466             EventServiceManager::getInstance().getSubscription(params[0]);
467         if (subValue == nullptr)
468         {
469             res.result(boost::beast::http::status::not_found);
470             res.end();
471             return;
472         }
473 
474         std::optional<std::string> context;
475         std::optional<std::string> retryPolicy;
476         std::optional<std::vector<nlohmann::json>> headers;
477 
478         if (!json_util::readJson(req, res, "Context", context,
479                                  "DeliveryRetryPolicy", retryPolicy,
480                                  "HttpHeaders", headers))
481         {
482             return;
483         }
484 
485         if (context)
486         {
487             subValue->customText = *context;
488         }
489 
490         if (headers)
491         {
492             subValue->httpHeaders = *headers;
493         }
494 
495         if (retryPolicy)
496         {
497             if (std::find(supportedRetryPolicies.begin(),
498                           supportedRetryPolicies.end(),
499                           *retryPolicy) == supportedRetryPolicies.end())
500             {
501                 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
502                                                  "DeliveryRetryPolicy");
503                 return;
504             }
505             subValue->retryPolicy = *retryPolicy;
506         }
507 
508         EventServiceManager::getInstance().updateSubscriptionData();
509     }
510 
511     void doDelete(crow::Response& res, const crow::Request& req,
512                   const std::vector<std::string>& params) override
513     {
514         auto asyncResp = std::make_shared<AsyncResp>(res);
515 
516         if (params.size() != 1)
517         {
518             messages::internalError(asyncResp->res);
519             return;
520         }
521 
522         if (!EventServiceManager::getInstance().isSubscriptionExist(params[0]))
523         {
524             res.result(boost::beast::http::status::not_found);
525             res.end();
526             return;
527         }
528         EventServiceManager::getInstance().deleteSubscription(params[0]);
529     }
530 };
531 
532 } // namespace redfish
533