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