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