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>> headers;
289         std::optional<std::vector<nlohmann::json>> 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::urls::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 aribitrarily.
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& headerChunk : *headers)
471             {
472                 std::string hdr{headerChunk.dump(
473                     -1, ' ', true, nlohmann::json::error_handler_t::replace)};
474                 cumulativeLen += hdr.length();
475 
476                 // This value is selected to mirror http_connection.hpp
477                 constexpr const uint16_t maxHeaderSizeED = 8096;
478                 if (cumulativeLen > maxHeaderSizeED)
479                 {
480                     messages::arraySizeTooLong(asyncResp->res, "HttpHeaders",
481                                                maxHeaderSizeED);
482                     return;
483                 }
484                 for (const auto& item : headerChunk.items())
485                 {
486                     const std::string* value =
487                         item.value().get_ptr<const std::string*>();
488                     if (value == nullptr)
489                     {
490                         messages::propertyValueFormatError(
491                             asyncResp->res, item.value(),
492                             "HttpHeaders/" + item.key());
493                         return;
494                     }
495                     subValue->httpHeaders.set(item.key(), *value);
496                 }
497             }
498         }
499 
500         if (regPrefixes)
501         {
502             for (const std::string& it : *regPrefixes)
503             {
504                 if (std::ranges::find(supportedRegPrefixes, it) ==
505                     supportedRegPrefixes.end())
506                 {
507                     messages::propertyValueNotInList(asyncResp->res, it,
508                                                      "RegistryPrefixes");
509                     return;
510                 }
511             }
512             subValue->registryPrefixes = *regPrefixes;
513         }
514 
515         if (resTypes)
516         {
517             for (const std::string& it : *resTypes)
518             {
519                 if (std::ranges::find(supportedResourceTypes, it) ==
520                     supportedResourceTypes.end())
521                 {
522                     messages::propertyValueNotInList(asyncResp->res, it,
523                                                      "ResourceTypes");
524                     return;
525                 }
526             }
527             subValue->resourceTypes = *resTypes;
528         }
529 
530         if (msgIds)
531         {
532             std::vector<std::string> registryPrefix;
533 
534             // If no registry prefixes are mentioned, consider all
535             // supported prefixes
536             if (subValue->registryPrefixes.empty())
537             {
538                 registryPrefix.assign(supportedRegPrefixes.begin(),
539                                       supportedRegPrefixes.end());
540             }
541             else
542             {
543                 registryPrefix = subValue->registryPrefixes;
544             }
545 
546             for (const std::string& id : *msgIds)
547             {
548                 bool validId = false;
549 
550                 // Check for Message ID in each of the selected Registry
551                 for (const std::string& it : registryPrefix)
552                 {
553                     const std::span<const redfish::registries::MessageEntry>
554                         registry =
555                             redfish::registries::getRegistryFromPrefix(it);
556 
557                     if (std::ranges::any_of(
558                             registry,
559                             [&id](const redfish::registries::MessageEntry&
560                                       messageEntry) {
561                         return id == messageEntry.first;
562                             }))
563                     {
564                         validId = true;
565                         break;
566                     }
567                 }
568 
569                 if (!validId)
570                 {
571                     messages::propertyValueNotInList(asyncResp->res, id,
572                                                      "MessageIds");
573                     return;
574                 }
575             }
576 
577             subValue->registryMsgIds = *msgIds;
578         }
579 
580         if (retryPolicy)
581         {
582             if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
583                 supportedRetryPolicies.end())
584             {
585                 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
586                                                  "DeliveryRetryPolicy");
587                 return;
588             }
589             subValue->retryPolicy = *retryPolicy;
590         }
591         else
592         {
593             // Default "TerminateAfterRetries"
594             subValue->retryPolicy = "TerminateAfterRetries";
595         }
596 
597         if (mrdJsonArray)
598         {
599             for (nlohmann::json& mrdObj : *mrdJsonArray)
600             {
601                 std::string mrdUri;
602 
603                 if (!json_util::readJson(mrdObj, asyncResp->res, "@odata.id",
604                                          mrdUri))
605 
606                 {
607                     return;
608                 }
609                 subValue->metricReportDefinitions.emplace_back(mrdUri);
610             }
611         }
612 
613         std::string id =
614             EventServiceManager::getInstance().addSubscription(subValue);
615         if (id.empty())
616         {
617             messages::internalError(asyncResp->res);
618             return;
619         }
620 
621         messages::created(asyncResp->res);
622         asyncResp->res.addHeader(
623             "Location", "/redfish/v1/EventService/Subscriptions/" + id);
624         });
625 }
626 
627 inline void requestRoutesEventDestination(App& app)
628 {
629     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
630         .privileges(redfish::privileges::getEventDestination)
631         .methods(boost::beast::http::verb::get)(
632             [&app](const crow::Request& req,
633                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
634                    const std::string& param) {
635         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
636         {
637             return;
638         }
639 
640         if (param.starts_with("snmp"))
641         {
642             getSnmpTrapClient(asyncResp, param);
643             return;
644         }
645 
646         std::shared_ptr<Subscription> subValue =
647             EventServiceManager::getInstance().getSubscription(param);
648         if (subValue == nullptr)
649         {
650             asyncResp->res.result(boost::beast::http::status::not_found);
651             return;
652         }
653         const std::string& id = param;
654 
655         asyncResp->res.jsonValue["@odata.type"] =
656             "#EventDestination.v1_8_0.EventDestination";
657         asyncResp->res.jsonValue["Protocol"] = "Redfish";
658         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
659             "/redfish/v1/EventService/Subscriptions/{}", id);
660         asyncResp->res.jsonValue["Id"] = id;
661         asyncResp->res.jsonValue["Name"] = "Event Destination " + id;
662         asyncResp->res.jsonValue["Destination"] = subValue->destinationUrl;
663         asyncResp->res.jsonValue["Context"] = subValue->customText;
664         asyncResp->res.jsonValue["SubscriptionType"] =
665             subValue->subscriptionType;
666         asyncResp->res.jsonValue["HttpHeaders"] = nlohmann::json::array();
667         asyncResp->res.jsonValue["EventFormatType"] = subValue->eventFormatType;
668         asyncResp->res.jsonValue["RegistryPrefixes"] =
669             subValue->registryPrefixes;
670         asyncResp->res.jsonValue["ResourceTypes"] = subValue->resourceTypes;
671 
672         asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds;
673         asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy;
674 
675         nlohmann::json::array_t mrdJsonArray;
676         for (const auto& mdrUri : subValue->metricReportDefinitions)
677         {
678             nlohmann::json::object_t mdr;
679             mdr["@odata.id"] = mdrUri;
680             mrdJsonArray.emplace_back(std::move(mdr));
681         }
682         asyncResp->res.jsonValue["MetricReportDefinitions"] = mrdJsonArray;
683         });
684     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
685         // The below privilege is wrong, it should be ConfigureManager OR
686         // ConfigureSelf
687         // https://github.com/openbmc/bmcweb/issues/220
688         //.privileges(redfish::privileges::patchEventDestination)
689         .privileges({{"ConfigureManager"}})
690         .methods(boost::beast::http::verb::patch)(
691             [&app](const crow::Request& req,
692                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
693                    const std::string& param) {
694         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
695         {
696             return;
697         }
698         std::shared_ptr<Subscription> subValue =
699             EventServiceManager::getInstance().getSubscription(param);
700         if (subValue == nullptr)
701         {
702             asyncResp->res.result(boost::beast::http::status::not_found);
703             return;
704         }
705 
706         std::optional<std::string> context;
707         std::optional<std::string> retryPolicy;
708         std::optional<std::vector<nlohmann::json>> headers;
709 
710         if (!json_util::readJsonPatch(req, asyncResp->res, "Context", context,
711                                       "DeliveryRetryPolicy", retryPolicy,
712                                       "HttpHeaders", headers))
713         {
714             return;
715         }
716 
717         if (context)
718         {
719             subValue->customText = *context;
720         }
721 
722         if (headers)
723         {
724             boost::beast::http::fields fields;
725             for (const nlohmann::json& headerChunk : *headers)
726             {
727                 for (const auto& it : headerChunk.items())
728                 {
729                     const std::string* value =
730                         it.value().get_ptr<const std::string*>();
731                     if (value == nullptr)
732                     {
733                         messages::propertyValueFormatError(
734                             asyncResp->res, it.value(),
735                             "HttpHeaders/" + it.key());
736                         return;
737                     }
738                     fields.set(it.key(), *value);
739                 }
740             }
741             subValue->httpHeaders = fields;
742         }
743 
744         if (retryPolicy)
745         {
746             if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
747                 supportedRetryPolicies.end())
748             {
749                 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
750                                                  "DeliveryRetryPolicy");
751                 return;
752             }
753             subValue->retryPolicy = *retryPolicy;
754         }
755 
756         EventServiceManager::getInstance().updateSubscriptionData();
757         });
758     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
759         // The below privilege is wrong, it should be ConfigureManager OR
760         // ConfigureSelf
761         // https://github.com/openbmc/bmcweb/issues/220
762         //.privileges(redfish::privileges::deleteEventDestination)
763         .privileges({{"ConfigureManager"}})
764         .methods(boost::beast::http::verb::delete_)(
765             [&app](const crow::Request& req,
766                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
767                    const std::string& param) {
768         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
769         {
770             return;
771         }
772 
773         if (param.starts_with("snmp"))
774         {
775             deleteSnmpTrapClient(asyncResp, param);
776             EventServiceManager::getInstance().deleteSubscription(param);
777             return;
778         }
779 
780         if (!EventServiceManager::getInstance().isSubscriptionExist(param))
781         {
782             asyncResp->res.result(boost::beast::http::status::not_found);
783             return;
784         }
785         EventServiceManager::getInstance().deleteSubscription(param);
786         });
787 }
788 
789 } // namespace redfish
790