xref: /openbmc/bmcweb/redfish-core/lib/event_service.hpp (revision 4a19a7b5e62ad2569913d944c84c19f1005c922f)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2020 Intel Corporation
4 #pragma once
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "dbus_singleton.hpp"
8 #include "dbus_utility.hpp"
9 #include "error_messages.hpp"
10 #include "event_service_manager.hpp"
11 #include "event_service_store.hpp"
12 #include "generated/enums/event_destination.hpp"
13 #include "http/utility.hpp"
14 #include "http_request.hpp"
15 #include "io_context_singleton.hpp"
16 #include "logging.hpp"
17 #include "query.hpp"
18 #include "registries.hpp"
19 #include "registries/privilege_registry.hpp"
20 #include "registries_selector.hpp"
21 #include "snmp_trap_event_clients.hpp"
22 #include "subscription.hpp"
23 #include "utils/json_utils.hpp"
24 
25 #include <asm-generic/errno.h>
26 
27 #include <boost/beast/http/fields.hpp>
28 #include <boost/beast/http/status.hpp>
29 #include <boost/beast/http/verb.hpp>
30 #include <boost/system/error_code.hpp>
31 #include <boost/system/result.hpp>
32 #include <boost/url/format.hpp>
33 #include <boost/url/parse.hpp>
34 #include <boost/url/url.hpp>
35 #include <sdbusplus/message/native_types.hpp>
36 
37 #include <algorithm>
38 #include <array>
39 #include <cerrno>
40 #include <cstddef>
41 #include <cstdint>
42 #include <memory>
43 #include <optional>
44 #include <ranges>
45 #include <span>
46 #include <string>
47 #include <utility>
48 #include <vector>
49 
50 namespace redfish
51 {
52 
53 static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
54     eventFormatType, metricReportFormatType};
55 static constexpr const std::array<const char*, 4> supportedRegPrefixes = {
56     "Base", "OpenBMC", "TaskEvent", "HeartbeatEvent"};
57 static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
58     "TerminateAfterRetries", "SuspendRetries", "RetryForever"};
59 
60 static constexpr const std::array<const char*, 2> supportedResourceTypes = {
61     "Task", "Heartbeat"};
62 
requestRoutesEventService(App & app)63 inline void requestRoutesEventService(App& app)
64 {
65     BMCWEB_ROUTE(app, "/redfish/v1/EventService/")
66         .privileges(redfish::privileges::getEventService)
67         .methods(
68             boost::beast::http::verb::
69                 get)([&app](
70                          const crow::Request& req,
71                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
72             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
73             {
74                 return;
75             }
76 
77             asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService";
78             asyncResp->res.jsonValue["@odata.type"] =
79                 "#EventService.v1_5_0.EventService";
80             asyncResp->res.jsonValue["Id"] = "EventService";
81             asyncResp->res.jsonValue["Name"] = "Event Service";
82             asyncResp->res.jsonValue["ServerSentEventUri"] =
83                 "/redfish/v1/EventService/SSE";
84 
85             asyncResp->res.jsonValue["Subscriptions"]["@odata.id"] =
86                 "/redfish/v1/EventService/Subscriptions";
87             asyncResp->res.jsonValue["Actions"]["#EventService.SubmitTestEvent"]
88                                     ["target"] =
89                 "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent";
90 
91             const persistent_data::EventServiceConfig eventServiceConfig =
92                 persistent_data::EventServiceStore::getInstance()
93                     .getEventServiceConfig();
94 
95             asyncResp->res.jsonValue["Status"]["State"] =
96                 (eventServiceConfig.enabled ? "Enabled" : "Disabled");
97             asyncResp->res.jsonValue["ServiceEnabled"] =
98                 eventServiceConfig.enabled;
99             asyncResp->res.jsonValue["DeliveryRetryAttempts"] =
100                 eventServiceConfig.retryAttempts;
101             asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] =
102                 eventServiceConfig.retryTimeoutInterval;
103             asyncResp->res.jsonValue["EventFormatTypes"] =
104                 supportedEvtFormatTypes;
105             asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes;
106             asyncResp->res.jsonValue["ResourceTypes"] = supportedResourceTypes;
107 
108             nlohmann::json::object_t supportedSSEFilters;
109             supportedSSEFilters["EventFormatType"] = true;
110             supportedSSEFilters["MessageId"] = true;
111             supportedSSEFilters["MetricReportDefinition"] = true;
112             supportedSSEFilters["RegistryPrefix"] = true;
113             supportedSSEFilters["OriginResource"] = false;
114             supportedSSEFilters["ResourceType"] = false;
115 
116             asyncResp->res.jsonValue["SSEFilterPropertiesSupported"] =
117                 std::move(supportedSSEFilters);
118         });
119 
120     BMCWEB_ROUTE(app, "/redfish/v1/EventService/")
121         .privileges(redfish::privileges::patchEventService)
122         .methods(boost::beast::http::verb::patch)(
123             [&app](const crow::Request& req,
124                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
125                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
126                 {
127                     return;
128                 }
129                 std::optional<bool> serviceEnabled;
130                 std::optional<uint32_t> retryAttemps;
131                 std::optional<uint32_t> retryInterval;
132                 if (!json_util::readJsonPatch(                         //
133                         req, asyncResp->res,                           //
134                         "DeliveryRetryAttempts", retryAttemps,         //
135                         "DeliveryRetryIntervalSeconds", retryInterval, //
136                         "ServiceEnabled", serviceEnabled               //
137                         ))
138                 {
139                     return;
140                 }
141 
142                 persistent_data::EventServiceConfig eventServiceConfig =
143                     persistent_data::EventServiceStore::getInstance()
144                         .getEventServiceConfig();
145 
146                 if (serviceEnabled)
147                 {
148                     eventServiceConfig.enabled = *serviceEnabled;
149                 }
150 
151                 if (retryAttemps)
152                 {
153                     // Supported range [1-3]
154                     if ((*retryAttemps < 1) || (*retryAttemps > 3))
155                     {
156                         messages::queryParameterOutOfRange(
157                             asyncResp->res, std::to_string(*retryAttemps),
158                             "DeliveryRetryAttempts", "[1-3]");
159                     }
160                     else
161                     {
162                         eventServiceConfig.retryAttempts = *retryAttemps;
163                     }
164                 }
165 
166                 if (retryInterval)
167                 {
168                     // Supported range [5 - 180]
169                     if ((*retryInterval < 5) || (*retryInterval > 180))
170                     {
171                         messages::queryParameterOutOfRange(
172                             asyncResp->res, std::to_string(*retryInterval),
173                             "DeliveryRetryIntervalSeconds", "[5-180]");
174                     }
175                     else
176                     {
177                         eventServiceConfig.retryTimeoutInterval =
178                             *retryInterval;
179                     }
180                 }
181 
182                 EventServiceManager::getInstance().setEventServiceConfig(
183                     eventServiceConfig);
184             });
185 }
186 
requestRoutesSubmitTestEvent(App & app)187 inline void requestRoutesSubmitTestEvent(App& app)
188 {
189     BMCWEB_ROUTE(
190         app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/")
191         .privileges(redfish::privileges::postEventService)
192         .methods(boost::beast::http::verb::post)(
193             [&app](const crow::Request& req,
194                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
195                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
196                 {
197                     return;
198                 }
199 
200                 // From the Redfish spec on EventId
201                 // A service can ignore this value and replace it with its own.
202                 // note that this parameter is intentionally ignored
203 
204                 std::optional<std::string> eventId;
205                 TestEvent testEvent;
206                 // clang-format off
207                 if (!json_util::readJsonAction(
208                         req, asyncResp->res,
209                         "EventGroupId", testEvent.eventGroupId,
210                         "EventId", eventId,
211                         "EventTimestamp", testEvent.eventTimestamp,
212                         "Message", testEvent.message,
213                         "MessageArgs", testEvent.messageArgs,
214                         "MessageId", testEvent.messageId,
215                         "OriginOfCondition", testEvent.originOfCondition,
216                         "Resolution", testEvent.resolution,
217                         "Severity", testEvent.severity))
218                 {
219                     return;
220                 }
221                 // clang-format on
222 
223                 if (!EventServiceManager::getInstance().sendTestEventLog(
224                         testEvent))
225                 {
226                     messages::serviceDisabled(asyncResp->res,
227                                               "/redfish/v1/EventService/");
228                     return;
229                 }
230                 asyncResp->res.result(boost::beast::http::status::no_content);
231             });
232 }
233 
doSubscriptionCollection(const boost::system::error_code & ec,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const dbus::utility::ManagedObjectType & resp)234 inline void doSubscriptionCollection(
235     const boost::system::error_code& ec,
236     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
237     const dbus::utility::ManagedObjectType& resp)
238 {
239     if (ec)
240     {
241         if (ec.value() == EBADR || ec.value() == EHOSTUNREACH)
242         {
243             // This is an optional process so just return if it isn't there
244             return;
245         }
246 
247         BMCWEB_LOG_ERROR("D-Bus response error on GetManagedObjects {}", ec);
248         messages::internalError(asyncResp->res);
249         return;
250     }
251     nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
252     for (const auto& objpath : resp)
253     {
254         sdbusplus::message::object_path path(objpath.first);
255         const std::string snmpId = path.filename();
256         if (snmpId.empty())
257         {
258             BMCWEB_LOG_ERROR("The SNMP client ID is wrong");
259             messages::internalError(asyncResp->res);
260             return;
261         }
262 
263         getSnmpSubscriptionList(asyncResp, snmpId, memberArray);
264     }
265 }
266 
requestRoutesEventDestinationCollection(App & app)267 inline void requestRoutesEventDestinationCollection(App& app)
268 {
269     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
270         .privileges(redfish::privileges::getEventDestinationCollection)
271         .methods(boost::beast::http::verb::get)(
272             [&app](const crow::Request& req,
273                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
274                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
275                 {
276                     return;
277                 }
278                 asyncResp->res.jsonValue["@odata.type"] =
279                     "#EventDestinationCollection.EventDestinationCollection";
280                 asyncResp->res.jsonValue["@odata.id"] =
281                     "/redfish/v1/EventService/Subscriptions";
282                 asyncResp->res.jsonValue["Name"] =
283                     "Event Destination Collections";
284 
285                 nlohmann::json& memberArray =
286                     asyncResp->res.jsonValue["Members"];
287 
288                 std::vector<std::string> subscripIds =
289                     EventServiceManager::getInstance().getAllIDs();
290                 memberArray = nlohmann::json::array();
291                 asyncResp->res.jsonValue["Members@odata.count"] =
292                     subscripIds.size();
293 
294                 for (const std::string& id : subscripIds)
295                 {
296                     nlohmann::json::object_t member;
297                     member["@odata.id"] = boost::urls::format(
298                         "/redfish/v1/EventService/Subscriptions/{}" + id);
299                     memberArray.emplace_back(std::move(member));
300                 }
301                 crow::connections::systemBus->async_method_call(
302                     [asyncResp](const boost::system::error_code& ec,
303                                 const dbus::utility::ManagedObjectType& resp) {
304                         doSubscriptionCollection(ec, asyncResp, resp);
305                     },
306                     "xyz.openbmc_project.Network.SNMP",
307                     "/xyz/openbmc_project/network/snmp/manager",
308                     "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
309             });
310 
311     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
312         .privileges(redfish::privileges::postEventDestinationCollection)
313         .methods(
314             boost::beast::http::verb::
315                 post)([&app](
316                           const crow::Request& req,
317                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
318             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
319             {
320                 return;
321             }
322             if (EventServiceManager::getInstance().getNumberOfSubscriptions() >=
323                 maxNoOfSubscriptions)
324             {
325                 messages::eventSubscriptionLimitExceeded(asyncResp->res);
326                 return;
327             }
328             std::string destUrl;
329             std::string protocol;
330             std::optional<bool> verifyCertificate;
331             std::optional<std::string> context;
332             std::optional<std::string> subscriptionType;
333             std::optional<std::string> eventFormatType2;
334             std::optional<std::string> retryPolicy;
335             std::optional<bool> sendHeartbeat;
336             std::optional<uint64_t> hbIntervalMinutes;
337             std::optional<std::vector<std::string>> msgIds;
338             std::optional<std::vector<std::string>> regPrefixes;
339             std::optional<std::vector<std::string>> originResources;
340             std::optional<std::vector<std::string>> resTypes;
341             std::optional<std::vector<nlohmann::json::object_t>> headers;
342             std::optional<std::vector<nlohmann::json::object_t>> mrdJsonArray;
343 
344             if (!json_util::readJsonPatch(                         //
345                     req, asyncResp->res,                           //
346                     "Context", context,                            //
347                     "DeliveryRetryPolicy", retryPolicy,            //
348                     "Destination", destUrl,                        //
349                     "EventFormatType", eventFormatType2,           //
350                     "HeartbeatIntervalMinutes", hbIntervalMinutes, //
351                     "HttpHeaders", headers,                        //
352                     "MessageIds", msgIds,                          //
353                     "MetricReportDefinitions", mrdJsonArray,       //
354                     "OriginResources", originResources,            //
355                     "Protocol", protocol,                          //
356                     "RegistryPrefixes", regPrefixes,               //
357                     "ResourceTypes", resTypes,                     //
358                     "SendHeartbeat", sendHeartbeat,                //
359                     "SubscriptionType", subscriptionType,          //
360                     "VerifyCertificate", verifyCertificate         //
361                     ))
362             {
363                 return;
364             }
365             // clang-format on
366 
367             // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
368             static constexpr const uint16_t maxDestinationSize = 2000;
369             if (destUrl.size() > maxDestinationSize)
370             {
371                 messages::stringValueTooLong(asyncResp->res, "Destination",
372                                              maxDestinationSize);
373                 return;
374             }
375 
376             if (regPrefixes && msgIds)
377             {
378                 if (!regPrefixes->empty() && !msgIds->empty())
379                 {
380                     messages::propertyValueConflict(
381                         asyncResp->res, "MessageIds", "RegistryPrefixes");
382                     return;
383                 }
384             }
385 
386             boost::system::result<boost::urls::url> url =
387                 boost::urls::parse_absolute_uri(destUrl);
388             if (!url)
389             {
390                 BMCWEB_LOG_WARNING(
391                     "Failed to validate and split destination url");
392                 messages::propertyValueFormatError(asyncResp->res, destUrl,
393                                                    "Destination");
394                 return;
395             }
396             url->normalize();
397 
398             // port_number returns zero if it is not a valid representable port
399             if (url->has_port() && url->port_number() == 0)
400             {
401                 BMCWEB_LOG_WARNING("{} is an invalid port in destination url",
402                                    url->port());
403                 messages::propertyValueFormatError(asyncResp->res, destUrl,
404                                                    "Destination");
405                 return;
406             }
407 
408             crow::utility::setProtocolDefaults(*url, protocol);
409             crow::utility::setPortDefaults(*url);
410 
411             if (url->path().empty())
412             {
413                 url->set_path("/");
414             }
415 
416             if (url->has_userinfo())
417             {
418                 messages::propertyValueFormatError(asyncResp->res, destUrl,
419                                                    "Destination");
420                 return;
421             }
422 
423             if (protocol == "SNMPv2c")
424             {
425                 if (context)
426                 {
427                     messages::propertyValueConflict(asyncResp->res, "Context",
428                                                     "Protocol");
429                     return;
430                 }
431                 if (eventFormatType2)
432                 {
433                     messages::propertyValueConflict(
434                         asyncResp->res, "EventFormatType", "Protocol");
435                     return;
436                 }
437                 if (retryPolicy)
438                 {
439                     messages::propertyValueConflict(asyncResp->res,
440                                                     "RetryPolicy", "Protocol");
441                     return;
442                 }
443                 if (sendHeartbeat)
444                 {
445                     messages::propertyValueConflict(
446                         asyncResp->res, "SendHeartbeat", "Protocol");
447                     return;
448                 }
449                 if (hbIntervalMinutes)
450                 {
451                     messages::propertyValueConflict(
452                         asyncResp->res, "HeartbeatIntervalMinutes", "Protocol");
453                     return;
454                 }
455                 if (msgIds)
456                 {
457                     messages::propertyValueConflict(asyncResp->res,
458                                                     "MessageIds", "Protocol");
459                     return;
460                 }
461                 if (regPrefixes)
462                 {
463                     messages::propertyValueConflict(
464                         asyncResp->res, "RegistryPrefixes", "Protocol");
465                     return;
466                 }
467                 if (resTypes)
468                 {
469                     messages::propertyValueConflict(
470                         asyncResp->res, "ResourceTypes", "Protocol");
471                     return;
472                 }
473                 if (headers)
474                 {
475                     messages::propertyValueConflict(asyncResp->res,
476                                                     "HttpHeaders", "Protocol");
477                     return;
478                 }
479                 if (mrdJsonArray)
480                 {
481                     messages::propertyValueConflict(
482                         asyncResp->res, "MetricReportDefinitions", "Protocol");
483                     return;
484                 }
485                 if (url->scheme() != "snmp")
486                 {
487                     messages::propertyValueConflict(asyncResp->res,
488                                                     "Destination", "Protocol");
489                     return;
490                 }
491 
492                 addSnmpTrapClient(asyncResp, url->host_address(),
493                                   url->port_number());
494                 return;
495             }
496 
497             std::shared_ptr<Subscription> subValue =
498                 std::make_shared<Subscription>(
499                     std::make_shared<persistent_data::UserSubscription>(), *url,
500                     getIoContext());
501 
502             if (subscriptionType)
503             {
504                 if (*subscriptionType != "RedfishEvent")
505                 {
506                     messages::propertyValueNotInList(
507                         asyncResp->res, *subscriptionType, "SubscriptionType");
508                     return;
509                 }
510                 subValue->userSub->subscriptionType = *subscriptionType;
511             }
512             else
513             {
514                 // Default
515                 subValue->userSub->subscriptionType = "RedfishEvent";
516             }
517 
518             if (protocol != "Redfish")
519             {
520                 messages::propertyValueNotInList(asyncResp->res, protocol,
521                                                  "Protocol");
522                 return;
523             }
524             subValue->userSub->protocol = protocol;
525 
526             if (verifyCertificate)
527             {
528                 subValue->userSub->verifyCertificate = *verifyCertificate;
529             }
530 
531             if (eventFormatType2)
532             {
533                 if (std::ranges::find(supportedEvtFormatTypes,
534                                       *eventFormatType2) ==
535                     supportedEvtFormatTypes.end())
536                 {
537                     messages::propertyValueNotInList(
538                         asyncResp->res, *eventFormatType2, "EventFormatType");
539                     return;
540                 }
541                 subValue->userSub->eventFormatType = *eventFormatType2;
542             }
543             else
544             {
545                 // If not specified, use default "Event"
546                 subValue->userSub->eventFormatType = "Event";
547             }
548 
549             if (context)
550             {
551                 // This value is selected arbitrarily.
552                 constexpr const size_t maxContextSize = 256;
553                 if (context->size() > maxContextSize)
554                 {
555                     messages::stringValueTooLong(asyncResp->res, "Context",
556                                                  maxContextSize);
557                     return;
558                 }
559                 subValue->userSub->customText = *context;
560             }
561 
562             if (headers)
563             {
564                 size_t cumulativeLen = 0;
565 
566                 for (const nlohmann::json::object_t& headerChunk : *headers)
567                 {
568                     for (const auto& item : headerChunk)
569                     {
570                         const std::string* value =
571                             item.second.get_ptr<const std::string*>();
572                         if (value == nullptr)
573                         {
574                             messages::propertyValueFormatError(
575                                 asyncResp->res, item.second,
576                                 "HttpHeaders/" + item.first);
577                             return;
578                         }
579                         // Adding a new json value is the size of the key, +
580                         // the size of the value + 2 * 2 quotes for each, +
581                         // the colon and space between. example:
582                         // "key": "value"
583                         cumulativeLen += item.first.size() + value->size() + 6;
584                         // This value is selected to mirror http_connection.hpp
585                         constexpr const uint16_t maxHeaderSizeED = 8096;
586                         if (cumulativeLen > maxHeaderSizeED)
587                         {
588                             messages::arraySizeTooLong(
589                                 asyncResp->res, "HttpHeaders", maxHeaderSizeED);
590                             return;
591                         }
592                         subValue->userSub->httpHeaders.set(item.first, *value);
593                     }
594                 }
595             }
596 
597             if (regPrefixes)
598             {
599                 for (const std::string& it : *regPrefixes)
600                 {
601                     if (std::ranges::find(supportedRegPrefixes, it) ==
602                         supportedRegPrefixes.end())
603                     {
604                         messages::propertyValueNotInList(asyncResp->res, it,
605                                                          "RegistryPrefixes");
606                         return;
607                     }
608                 }
609                 subValue->userSub->registryPrefixes = *regPrefixes;
610             }
611 
612             if (originResources)
613             {
614                 subValue->userSub->originResources = *originResources;
615             }
616 
617             if (resTypes)
618             {
619                 for (const std::string& it : *resTypes)
620                 {
621                     if (std::ranges::find(supportedResourceTypes, it) ==
622                         supportedResourceTypes.end())
623                     {
624                         messages::propertyValueNotInList(asyncResp->res, it,
625                                                          "ResourceTypes");
626                         return;
627                     }
628                 }
629                 subValue->userSub->resourceTypes = *resTypes;
630             }
631 
632             if (msgIds)
633             {
634                 std::vector<std::string> registryPrefix;
635 
636                 // If no registry prefixes are mentioned, consider all
637                 // supported prefixes
638                 if (subValue->userSub->registryPrefixes.empty())
639                 {
640                     registryPrefix.assign(supportedRegPrefixes.begin(),
641                                           supportedRegPrefixes.end());
642                 }
643                 else
644                 {
645                     registryPrefix = subValue->userSub->registryPrefixes;
646                 }
647 
648                 for (const std::string& id : *msgIds)
649                 {
650                     bool validId = false;
651 
652                     // Check for Message ID in each of the selected Registry
653                     for (const std::string& it : registryPrefix)
654                     {
655                         const std::span<const redfish::registries::MessageEntry>
656                             registry =
657                                 redfish::registries::getRegistryFromPrefix(it);
658 
659                         if (std::ranges::any_of(
660                                 registry,
661                                 [&id](const redfish::registries::MessageEntry&
662                                           messageEntry) {
663                                     return id == messageEntry.first;
664                                 }))
665                         {
666                             validId = true;
667                             break;
668                         }
669                     }
670 
671                     if (!validId)
672                     {
673                         messages::propertyValueNotInList(asyncResp->res, id,
674                                                          "MessageIds");
675                         return;
676                     }
677                 }
678 
679                 subValue->userSub->registryMsgIds = *msgIds;
680             }
681 
682             if (retryPolicy)
683             {
684                 if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
685                     supportedRetryPolicies.end())
686                 {
687                     messages::propertyValueNotInList(
688                         asyncResp->res, *retryPolicy, "DeliveryRetryPolicy");
689                     return;
690                 }
691                 subValue->userSub->retryPolicy = *retryPolicy;
692             }
693             else
694             {
695                 // Default "TerminateAfterRetries"
696                 subValue->userSub->retryPolicy = "TerminateAfterRetries";
697             }
698             if (sendHeartbeat)
699             {
700                 subValue->userSub->sendHeartbeat = *sendHeartbeat;
701             }
702             if (hbIntervalMinutes)
703             {
704                 if (*hbIntervalMinutes < 1 || *hbIntervalMinutes > 65535)
705                 {
706                     messages::propertyValueOutOfRange(
707                         asyncResp->res, *hbIntervalMinutes,
708                         "HeartbeatIntervalMinutes");
709                     return;
710                 }
711                 subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes;
712             }
713 
714             if (mrdJsonArray)
715             {
716                 for (nlohmann::json::object_t& mrdObj : *mrdJsonArray)
717                 {
718                     std::string mrdUri;
719 
720                     if (!json_util::readJsonObject(mrdObj, asyncResp->res,
721                                                    "@odata.id", mrdUri))
722 
723                     {
724                         return;
725                     }
726                     subValue->userSub->metricReportDefinitions.emplace_back(
727                         mrdUri);
728                 }
729             }
730 
731             std::string id =
732                 EventServiceManager::getInstance().addPushSubscription(
733                     subValue);
734             if (id.empty())
735             {
736                 messages::internalError(asyncResp->res);
737                 return;
738             }
739 
740             messages::created(asyncResp->res);
741             asyncResp->res.addHeader(
742                 "Location", "/redfish/v1/EventService/Subscriptions/" + id);
743 
744             // schedule a heartbeat
745             if (subValue->userSub->sendHeartbeat)
746             {
747                 subValue->scheduleNextHeartbeatEvent();
748             }
749         });
750 }
751 
requestRoutesEventDestination(App & app)752 inline void requestRoutesEventDestination(App& app)
753 {
754     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
755         .privileges(redfish::privileges::getEventDestination)
756         .methods(boost::beast::http::verb::get)(
757             [&app](const crow::Request& req,
758                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
759                    const std::string& param) {
760                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
761                 {
762                     return;
763                 }
764 
765                 if (param.starts_with("snmp"))
766                 {
767                     getSnmpTrapClient(asyncResp, param);
768                     return;
769                 }
770 
771                 std::shared_ptr<Subscription> subValue =
772                     EventServiceManager::getInstance().getSubscription(param);
773                 if (subValue == nullptr)
774                 {
775                     asyncResp->res.result(
776                         boost::beast::http::status::not_found);
777                     return;
778                 }
779                 const std::string& id = param;
780 
781                 const persistent_data::UserSubscription& userSub =
782                     *subValue->userSub;
783 
784                 nlohmann::json& jVal = asyncResp->res.jsonValue;
785                 jVal["@odata.type"] =
786                     "#EventDestination.v1_14_1.EventDestination";
787                 jVal["Protocol"] =
788                     event_destination::EventDestinationProtocol::Redfish;
789                 jVal["@odata.id"] = boost::urls::format(
790                     "/redfish/v1/EventService/Subscriptions/{}", id);
791                 jVal["Id"] = id;
792                 jVal["Name"] = "Event Destination " + id;
793                 jVal["Destination"] = userSub.destinationUrl;
794                 jVal["Context"] = userSub.customText;
795                 jVal["SubscriptionType"] = userSub.subscriptionType;
796                 jVal["HttpHeaders"] = nlohmann::json::array();
797                 jVal["EventFormatType"] = userSub.eventFormatType;
798                 jVal["RegistryPrefixes"] = userSub.registryPrefixes;
799                 jVal["ResourceTypes"] = userSub.resourceTypes;
800 
801                 jVal["MessageIds"] = userSub.registryMsgIds;
802                 jVal["DeliveryRetryPolicy"] = userSub.retryPolicy;
803                 jVal["SendHeartbeat"] = userSub.sendHeartbeat;
804                 jVal["HeartbeatIntervalMinutes"] = userSub.hbIntervalMinutes;
805                 jVal["VerifyCertificate"] = userSub.verifyCertificate;
806 
807                 nlohmann::json::array_t mrdJsonArray;
808                 for (const auto& mdrUri : userSub.metricReportDefinitions)
809                 {
810                     nlohmann::json::object_t mdr;
811                     mdr["@odata.id"] = mdrUri;
812                     mrdJsonArray.emplace_back(std::move(mdr));
813                 }
814                 jVal["MetricReportDefinitions"] = mrdJsonArray;
815             });
816     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
817         // The below privilege is wrong, it should be ConfigureManager OR
818         // ConfigureSelf
819         // https://github.com/openbmc/bmcweb/issues/220
820         //.privileges(redfish::privileges::patchEventDestination)
821         .privileges({{"ConfigureManager"}})
822         .methods(boost::beast::http::verb::patch)(
823             [&app](const crow::Request& req,
824                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
825                    const std::string& param) {
826                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
827                 {
828                     return;
829                 }
830                 std::shared_ptr<Subscription> subValue =
831                     EventServiceManager::getInstance().getSubscription(param);
832                 if (subValue == nullptr)
833                 {
834                     asyncResp->res.result(
835                         boost::beast::http::status::not_found);
836                     return;
837                 }
838 
839                 std::optional<std::string> context;
840                 std::optional<std::string> retryPolicy;
841                 std::optional<bool> sendHeartbeat;
842                 std::optional<uint64_t> hbIntervalMinutes;
843                 std::optional<bool> verifyCertificate;
844                 std::optional<std::vector<nlohmann::json::object_t>> headers;
845 
846                 if (!json_util::readJsonPatch(                         //
847                         req, asyncResp->res,                           //
848                         "Context", context,                            //
849                         "DeliveryRetryPolicy", retryPolicy,            //
850                         "HeartbeatIntervalMinutes", hbIntervalMinutes, //
851                         "HttpHeaders", headers,                        //
852                         "SendHeartbeat", sendHeartbeat,                //
853                         "VerifyCertificate", verifyCertificate         //
854                         ))
855                 {
856                     return;
857                 }
858 
859                 if (context)
860                 {
861                     subValue->userSub->customText = *context;
862                 }
863 
864                 if (headers)
865                 {
866                     boost::beast::http::fields fields;
867                     for (const nlohmann::json::object_t& headerChunk : *headers)
868                     {
869                         for (const auto& it : headerChunk)
870                         {
871                             const std::string* value =
872                                 it.second.get_ptr<const std::string*>();
873                             if (value == nullptr)
874                             {
875                                 messages::propertyValueFormatError(
876                                     asyncResp->res, it.second,
877                                     "HttpHeaders/" + it.first);
878                                 return;
879                             }
880                             fields.set(it.first, *value);
881                         }
882                     }
883                     subValue->userSub->httpHeaders = std::move(fields);
884                 }
885 
886                 if (retryPolicy)
887                 {
888                     if (std::ranges::find(supportedRetryPolicies,
889                                           *retryPolicy) ==
890                         supportedRetryPolicies.end())
891                     {
892                         messages::propertyValueNotInList(asyncResp->res,
893                                                          *retryPolicy,
894                                                          "DeliveryRetryPolicy");
895                         return;
896                     }
897                     subValue->userSub->retryPolicy = *retryPolicy;
898                 }
899 
900                 if (sendHeartbeat)
901                 {
902                     subValue->userSub->sendHeartbeat = *sendHeartbeat;
903                 }
904                 if (hbIntervalMinutes)
905                 {
906                     if (*hbIntervalMinutes < 1 || *hbIntervalMinutes > 65535)
907                     {
908                         messages::propertyValueOutOfRange(
909                             asyncResp->res, *hbIntervalMinutes,
910                             "HeartbeatIntervalMinutes");
911                         return;
912                     }
913                     subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes;
914                 }
915 
916                 if (hbIntervalMinutes || sendHeartbeat)
917                 {
918                     // if Heartbeat interval or send heart were changed, cancel
919                     // the heartbeat timer if running and start a new heartbeat
920                     // if needed
921                     subValue->heartbeatParametersChanged();
922                 }
923 
924                 if (verifyCertificate)
925                 {
926                     subValue->userSub->verifyCertificate = *verifyCertificate;
927                 }
928 
929                 EventServiceManager::getInstance().updateSubscriptionData();
930             });
931     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
932         // The below privilege is wrong, it should be ConfigureManager OR
933         // ConfigureSelf
934         // https://github.com/openbmc/bmcweb/issues/220
935         //.privileges(redfish::privileges::deleteEventDestination)
936         .privileges({{"ConfigureManager"}})
937         .methods(boost::beast::http::verb::delete_)(
938             [&app](const crow::Request& req,
939                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
940                    const std::string& param) {
941                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
942                 {
943                     return;
944                 }
945                 EventServiceManager& event = EventServiceManager::getInstance();
946                 if (param.starts_with("snmp"))
947                 {
948                     deleteSnmpTrapClient(asyncResp, param);
949                     event.deleteSubscription(param);
950                     return;
951                 }
952 
953                 if (!event.deleteSubscription(param))
954                 {
955                     messages::resourceNotFound(asyncResp->res,
956                                                "EventDestination", param);
957                     return;
958                 }
959                 messages::success(asyncResp->res);
960             });
961 }
962 
963 } // namespace redfish
964