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