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