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