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<bool> verifyCertificate;
282         std::optional<std::string> context;
283         std::optional<std::string> subscriptionType;
284         std::optional<std::string> eventFormatType2;
285         std::optional<std::string> retryPolicy;
286         std::optional<std::vector<std::string>> msgIds;
287         std::optional<std::vector<std::string>> regPrefixes;
288         std::optional<std::vector<std::string>> resTypes;
289         std::optional<std::vector<nlohmann::json::object_t>> headers;
290         std::optional<std::vector<nlohmann::json::object_t>> mrdJsonArray;
291 
292         if (!json_util::readJsonPatch(
293                 req, asyncResp->res, "Destination", destUrl, "Context", context,
294                 "Protocol", protocol, "SubscriptionType", subscriptionType,
295                 "EventFormatType", eventFormatType2, "HttpHeaders", headers,
296                 "RegistryPrefixes", regPrefixes, "MessageIds", msgIds,
297                 "DeliveryRetryPolicy", retryPolicy, "MetricReportDefinitions",
298                 mrdJsonArray, "ResourceTypes", resTypes, "VerifyCertificate",
299                 verifyCertificate))
300         {
301             return;
302         }
303 
304         // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
305         static constexpr const uint16_t maxDestinationSize = 2000;
306         if (destUrl.size() > maxDestinationSize)
307         {
308             messages::stringValueTooLong(asyncResp->res, "Destination",
309                                          maxDestinationSize);
310             return;
311         }
312 
313         if (regPrefixes && msgIds)
314         {
315             if (!regPrefixes->empty() && !msgIds->empty())
316             {
317                 messages::propertyValueConflict(asyncResp->res, "MessageIds",
318                                                 "RegistryPrefixes");
319                 return;
320             }
321         }
322 
323         boost::system::result<boost::urls::url> url =
324             boost::urls::parse_absolute_uri(destUrl);
325         if (!url)
326         {
327             BMCWEB_LOG_WARNING("Failed to validate and split destination url");
328             messages::propertyValueFormatError(asyncResp->res, destUrl,
329                                                "Destination");
330             return;
331         }
332         url->normalize();
333         crow::utility::setProtocolDefaults(*url, protocol);
334         crow::utility::setPortDefaults(*url);
335 
336         if (url->path().empty())
337         {
338             url->set_path("/");
339         }
340 
341         if (url->has_userinfo())
342         {
343             messages::propertyValueFormatError(asyncResp->res, destUrl,
344                                                "Destination");
345             return;
346         }
347 
348         if (protocol == "SNMPv2c")
349         {
350             if (context)
351             {
352                 messages::propertyValueConflict(asyncResp->res, "Context",
353                                                 "Protocol");
354                 return;
355             }
356             if (eventFormatType2)
357             {
358                 messages::propertyValueConflict(asyncResp->res,
359                                                 "EventFormatType", "Protocol");
360                 return;
361             }
362             if (retryPolicy)
363             {
364                 messages::propertyValueConflict(asyncResp->res, "RetryPolicy",
365                                                 "Protocol");
366                 return;
367             }
368             if (msgIds)
369             {
370                 messages::propertyValueConflict(asyncResp->res, "MessageIds",
371                                                 "Protocol");
372                 return;
373             }
374             if (regPrefixes)
375             {
376                 messages::propertyValueConflict(asyncResp->res,
377                                                 "RegistryPrefixes", "Protocol");
378                 return;
379             }
380             if (resTypes)
381             {
382                 messages::propertyValueConflict(asyncResp->res, "ResourceTypes",
383                                                 "Protocol");
384                 return;
385             }
386             if (headers)
387             {
388                 messages::propertyValueConflict(asyncResp->res, "HttpHeaders",
389                                                 "Protocol");
390                 return;
391             }
392             if (mrdJsonArray)
393             {
394                 messages::propertyValueConflict(
395                     asyncResp->res, "MetricReportDefinitions", "Protocol");
396                 return;
397             }
398             if (url->scheme() != "snmp")
399             {
400                 messages::propertyValueConflict(asyncResp->res, "Destination",
401                                                 "Protocol");
402                 return;
403             }
404 
405             addSnmpTrapClient(asyncResp, url->host_address(),
406                               url->port_number());
407             return;
408         }
409 
410         std::shared_ptr<Subscription> subValue =
411             std::make_shared<Subscription>(*url, app.ioContext());
412 
413         subValue->destinationUrl = std::move(*url);
414 
415         if (subscriptionType)
416         {
417             if (*subscriptionType != "RedfishEvent")
418             {
419                 messages::propertyValueNotInList(
420                     asyncResp->res, *subscriptionType, "SubscriptionType");
421                 return;
422             }
423             subValue->subscriptionType = *subscriptionType;
424         }
425         else
426         {
427             subValue->subscriptionType = "RedfishEvent"; // Default
428         }
429 
430         if (protocol != "Redfish")
431         {
432             messages::propertyValueNotInList(asyncResp->res, protocol,
433                                              "Protocol");
434             return;
435         }
436         subValue->protocol = protocol;
437 
438         if (verifyCertificate)
439         {
440             subValue->verifyCertificate = *verifyCertificate;
441         }
442 
443         if (eventFormatType2)
444         {
445             if (std::ranges::find(supportedEvtFormatTypes, *eventFormatType2) ==
446                 supportedEvtFormatTypes.end())
447             {
448                 messages::propertyValueNotInList(
449                     asyncResp->res, *eventFormatType2, "EventFormatType");
450                 return;
451             }
452             subValue->eventFormatType = *eventFormatType2;
453         }
454         else
455         {
456             // If not specified, use default "Event"
457             subValue->eventFormatType = "Event";
458         }
459 
460         if (context)
461         {
462             // This value is selected arbitrarily.
463             constexpr const size_t maxContextSize = 256;
464             if (context->size() > maxContextSize)
465             {
466                 messages::stringValueTooLong(asyncResp->res, "Context",
467                                              maxContextSize);
468                 return;
469             }
470             subValue->customText = *context;
471         }
472 
473         if (headers)
474         {
475             size_t cumulativeLen = 0;
476 
477             for (const nlohmann::json::object_t& headerChunk : *headers)
478             {
479                 for (const auto& item : headerChunk)
480                 {
481                     const std::string* value =
482                         item.second.get_ptr<const std::string*>();
483                     if (value == nullptr)
484                     {
485                         messages::propertyValueFormatError(
486                             asyncResp->res, item.second,
487                             "HttpHeaders/" + item.first);
488                         return;
489                     }
490                     // Adding a new json value is the size of the key, +
491                     // the size of the value + 2 * 2 quotes for each, +
492                     // the colon and space between. example:
493                     // "key": "value"
494                     cumulativeLen += item.first.size() + value->size() + 6;
495                     // This value is selected to mirror http_connection.hpp
496                     constexpr const uint16_t maxHeaderSizeED = 8096;
497                     if (cumulativeLen > maxHeaderSizeED)
498                     {
499                         messages::arraySizeTooLong(
500                             asyncResp->res, "HttpHeaders", maxHeaderSizeED);
501                         return;
502                     }
503                     subValue->httpHeaders.set(item.first, *value);
504                 }
505             }
506         }
507 
508         if (regPrefixes)
509         {
510             for (const std::string& it : *regPrefixes)
511             {
512                 if (std::ranges::find(supportedRegPrefixes, it) ==
513                     supportedRegPrefixes.end())
514                 {
515                     messages::propertyValueNotInList(asyncResp->res, it,
516                                                      "RegistryPrefixes");
517                     return;
518                 }
519             }
520             subValue->registryPrefixes = *regPrefixes;
521         }
522 
523         if (resTypes)
524         {
525             for (const std::string& it : *resTypes)
526             {
527                 if (std::ranges::find(supportedResourceTypes, it) ==
528                     supportedResourceTypes.end())
529                 {
530                     messages::propertyValueNotInList(asyncResp->res, it,
531                                                      "ResourceTypes");
532                     return;
533                 }
534             }
535             subValue->resourceTypes = *resTypes;
536         }
537 
538         if (msgIds)
539         {
540             std::vector<std::string> registryPrefix;
541 
542             // If no registry prefixes are mentioned, consider all
543             // supported prefixes
544             if (subValue->registryPrefixes.empty())
545             {
546                 registryPrefix.assign(supportedRegPrefixes.begin(),
547                                       supportedRegPrefixes.end());
548             }
549             else
550             {
551                 registryPrefix = subValue->registryPrefixes;
552             }
553 
554             for (const std::string& id : *msgIds)
555             {
556                 bool validId = false;
557 
558                 // Check for Message ID in each of the selected Registry
559                 for (const std::string& it : registryPrefix)
560                 {
561                     const std::span<const redfish::registries::MessageEntry>
562                         registry =
563                             redfish::registries::getRegistryFromPrefix(it);
564 
565                     if (std::ranges::any_of(
566                             registry,
567                             [&id](const redfish::registries::MessageEntry&
568                                       messageEntry) {
569                         return id == messageEntry.first;
570                     }))
571                     {
572                         validId = true;
573                         break;
574                     }
575                 }
576 
577                 if (!validId)
578                 {
579                     messages::propertyValueNotInList(asyncResp->res, id,
580                                                      "MessageIds");
581                     return;
582                 }
583             }
584 
585             subValue->registryMsgIds = *msgIds;
586         }
587 
588         if (retryPolicy)
589         {
590             if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
591                 supportedRetryPolicies.end())
592             {
593                 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
594                                                  "DeliveryRetryPolicy");
595                 return;
596             }
597             subValue->retryPolicy = *retryPolicy;
598         }
599         else
600         {
601             // Default "TerminateAfterRetries"
602             subValue->retryPolicy = "TerminateAfterRetries";
603         }
604 
605         if (mrdJsonArray)
606         {
607             for (nlohmann::json::object_t& mrdObj : *mrdJsonArray)
608             {
609                 std::string mrdUri;
610 
611                 if (!json_util::readJsonObject(mrdObj, asyncResp->res,
612                                                "@odata.id", mrdUri))
613 
614                 {
615                     return;
616                 }
617                 subValue->metricReportDefinitions.emplace_back(mrdUri);
618             }
619         }
620 
621         std::string id =
622             EventServiceManager::getInstance().addSubscription(subValue);
623         if (id.empty())
624         {
625             messages::internalError(asyncResp->res);
626             return;
627         }
628 
629         messages::created(asyncResp->res);
630         asyncResp->res.addHeader(
631             "Location", "/redfish/v1/EventService/Subscriptions/" + id);
632     });
633 }
634 
635 inline void requestRoutesEventDestination(App& app)
636 {
637     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
638         .privileges(redfish::privileges::getEventDestination)
639         .methods(boost::beast::http::verb::get)(
640             [&app](const crow::Request& req,
641                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
642                    const std::string& param) {
643         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
644         {
645             return;
646         }
647 
648         if (param.starts_with("snmp"))
649         {
650             getSnmpTrapClient(asyncResp, param);
651             return;
652         }
653 
654         std::shared_ptr<Subscription> subValue =
655             EventServiceManager::getInstance().getSubscription(param);
656         if (subValue == nullptr)
657         {
658             asyncResp->res.result(boost::beast::http::status::not_found);
659             return;
660         }
661         const std::string& id = param;
662 
663         asyncResp->res.jsonValue["@odata.type"] =
664             "#EventDestination.v1_8_0.EventDestination";
665         asyncResp->res.jsonValue["Protocol"] = "Redfish";
666         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
667             "/redfish/v1/EventService/Subscriptions/{}", id);
668         asyncResp->res.jsonValue["Id"] = id;
669         asyncResp->res.jsonValue["Name"] = "Event Destination " + id;
670         asyncResp->res.jsonValue["Destination"] = subValue->destinationUrl;
671         asyncResp->res.jsonValue["Context"] = subValue->customText;
672         asyncResp->res.jsonValue["SubscriptionType"] =
673             subValue->subscriptionType;
674         asyncResp->res.jsonValue["HttpHeaders"] = nlohmann::json::array();
675         asyncResp->res.jsonValue["EventFormatType"] = subValue->eventFormatType;
676         asyncResp->res.jsonValue["RegistryPrefixes"] =
677             subValue->registryPrefixes;
678         asyncResp->res.jsonValue["ResourceTypes"] = subValue->resourceTypes;
679 
680         asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds;
681         asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy;
682         asyncResp->res.jsonValue["VerifyCertificate"] =
683             subValue->verifyCertificate;
684 
685         nlohmann::json::array_t mrdJsonArray;
686         for (const auto& mdrUri : subValue->metricReportDefinitions)
687         {
688             nlohmann::json::object_t mdr;
689             mdr["@odata.id"] = mdrUri;
690             mrdJsonArray.emplace_back(std::move(mdr));
691         }
692         asyncResp->res.jsonValue["MetricReportDefinitions"] = mrdJsonArray;
693     });
694     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
695         // The below privilege is wrong, it should be ConfigureManager OR
696         // ConfigureSelf
697         // https://github.com/openbmc/bmcweb/issues/220
698         //.privileges(redfish::privileges::patchEventDestination)
699         .privileges({{"ConfigureManager"}})
700         .methods(boost::beast::http::verb::patch)(
701             [&app](const crow::Request& req,
702                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
703                    const std::string& param) {
704         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
705         {
706             return;
707         }
708         std::shared_ptr<Subscription> subValue =
709             EventServiceManager::getInstance().getSubscription(param);
710         if (subValue == nullptr)
711         {
712             asyncResp->res.result(boost::beast::http::status::not_found);
713             return;
714         }
715 
716         std::optional<std::string> context;
717         std::optional<std::string> retryPolicy;
718         std::optional<bool> verifyCertificate;
719         std::optional<std::vector<nlohmann::json::object_t>> headers;
720 
721         if (!json_util::readJsonPatch(req, asyncResp->res, "Context", context,
722                                       "VerifyCertificate", verifyCertificate,
723                                       "DeliveryRetryPolicy", retryPolicy,
724                                       "HttpHeaders", headers))
725         {
726             return;
727         }
728 
729         if (context)
730         {
731             subValue->customText = *context;
732         }
733 
734         if (headers)
735         {
736             boost::beast::http::fields fields;
737             for (const nlohmann::json::object_t& headerChunk : *headers)
738             {
739                 for (const auto& it : headerChunk)
740                 {
741                     const std::string* value =
742                         it.second.get_ptr<const std::string*>();
743                     if (value == nullptr)
744                     {
745                         messages::propertyValueFormatError(
746                             asyncResp->res, it.second,
747                             "HttpHeaders/" + it.first);
748                         return;
749                     }
750                     fields.set(it.first, *value);
751                 }
752             }
753             subValue->httpHeaders = std::move(fields);
754         }
755 
756         if (retryPolicy)
757         {
758             if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
759                 supportedRetryPolicies.end())
760             {
761                 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
762                                                  "DeliveryRetryPolicy");
763                 return;
764             }
765             subValue->retryPolicy = *retryPolicy;
766         }
767 
768         if (verifyCertificate)
769         {
770             subValue->verifyCertificate = *verifyCertificate;
771         }
772 
773         EventServiceManager::getInstance().updateSubscriptionData();
774     });
775     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
776         // The below privilege is wrong, it should be ConfigureManager OR
777         // ConfigureSelf
778         // https://github.com/openbmc/bmcweb/issues/220
779         //.privileges(redfish::privileges::deleteEventDestination)
780         .privileges({{"ConfigureManager"}})
781         .methods(boost::beast::http::verb::delete_)(
782             [&app](const crow::Request& req,
783                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
784                    const std::string& param) {
785         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
786         {
787             return;
788         }
789 
790         if (param.starts_with("snmp"))
791         {
792             deleteSnmpTrapClient(asyncResp, param);
793             EventServiceManager::getInstance().deleteSubscription(param);
794             return;
795         }
796 
797         if (!EventServiceManager::getInstance().isSubscriptionExist(param))
798         {
799             asyncResp->res.result(boost::beast::http::status::not_found);
800             return;
801         }
802         EventServiceManager::getInstance().deleteSubscription(param);
803     });
804 }
805 
806 } // namespace redfish
807