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