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