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