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