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                 if (!json_util::readJsonPatch( //
122                         req, asyncResp->res, //
123                         "DeliveryRetryAttempts", retryAttemps, //
124                         "DeliveryRetryIntervalSeconds", retryInterval, //
125                         "ServiceEnabled", serviceEnabled //
126                         ))
127                 {
128                     return;
129                 }
130 
131                 persistent_data::EventServiceConfig eventServiceConfig =
132                     persistent_data::EventServiceStore::getInstance()
133                         .getEventServiceConfig();
134 
135                 if (serviceEnabled)
136                 {
137                     eventServiceConfig.enabled = *serviceEnabled;
138                 }
139 
140                 if (retryAttemps)
141                 {
142                     // Supported range [1-3]
143                     if ((*retryAttemps < 1) || (*retryAttemps > 3))
144                     {
145                         messages::queryParameterOutOfRange(
146                             asyncResp->res, std::to_string(*retryAttemps),
147                             "DeliveryRetryAttempts", "[1-3]");
148                     }
149                     else
150                     {
151                         eventServiceConfig.retryAttempts = *retryAttemps;
152                     }
153                 }
154 
155                 if (retryInterval)
156                 {
157                     // Supported range [5 - 180]
158                     if ((*retryInterval < 5) || (*retryInterval > 180))
159                     {
160                         messages::queryParameterOutOfRange(
161                             asyncResp->res, std::to_string(*retryInterval),
162                             "DeliveryRetryIntervalSeconds", "[5-180]");
163                     }
164                     else
165                     {
166                         eventServiceConfig.retryTimeoutInterval =
167                             *retryInterval;
168                     }
169                 }
170 
171                 EventServiceManager::getInstance().setEventServiceConfig(
172                     eventServiceConfig);
173             });
174 }
175 
176 inline void requestRoutesSubmitTestEvent(App& app)
177 {
178     BMCWEB_ROUTE(
179         app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/")
180         .privileges(redfish::privileges::postEventService)
181         .methods(boost::beast::http::verb::post)(
182             [&app](const crow::Request& req,
183                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
184                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
185                 {
186                     return;
187                 }
188                 if (!EventServiceManager::getInstance().sendTestEventLog())
189                 {
190                     messages::serviceDisabled(asyncResp->res,
191                                               "/redfish/v1/EventService/");
192                     return;
193                 }
194                 asyncResp->res.result(boost::beast::http::status::no_content);
195             });
196 }
197 
198 inline void doSubscriptionCollection(
199     const boost::system::error_code& ec,
200     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
201     const dbus::utility::ManagedObjectType& resp)
202 {
203     if (ec)
204     {
205         if (ec.value() == EBADR || ec.value() == EHOSTUNREACH)
206         {
207             // This is an optional process so just return if it isn't there
208             return;
209         }
210 
211         BMCWEB_LOG_ERROR("D-Bus response error on GetManagedObjects {}", ec);
212         messages::internalError(asyncResp->res);
213         return;
214     }
215     nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
216     for (const auto& objpath : resp)
217     {
218         sdbusplus::message::object_path path(objpath.first);
219         const std::string snmpId = path.filename();
220         if (snmpId.empty())
221         {
222             BMCWEB_LOG_ERROR("The SNMP client ID is wrong");
223             messages::internalError(asyncResp->res);
224             return;
225         }
226 
227         getSnmpSubscriptionList(asyncResp, snmpId, memberArray);
228     }
229 }
230 
231 inline void requestRoutesEventDestinationCollection(App& app)
232 {
233     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
234         .privileges(redfish::privileges::getEventDestinationCollection)
235         .methods(boost::beast::http::verb::get)(
236             [&app](const crow::Request& req,
237                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
238                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
239                 {
240                     return;
241                 }
242                 asyncResp->res.jsonValue["@odata.type"] =
243                     "#EventDestinationCollection.EventDestinationCollection";
244                 asyncResp->res.jsonValue["@odata.id"] =
245                     "/redfish/v1/EventService/Subscriptions";
246                 asyncResp->res.jsonValue["Name"] =
247                     "Event Destination Collections";
248 
249                 nlohmann::json& memberArray =
250                     asyncResp->res.jsonValue["Members"];
251 
252                 std::vector<std::string> subscripIds =
253                     EventServiceManager::getInstance().getAllIDs();
254                 memberArray = nlohmann::json::array();
255                 asyncResp->res.jsonValue["Members@odata.count"] =
256                     subscripIds.size();
257 
258                 for (const std::string& id : subscripIds)
259                 {
260                     nlohmann::json::object_t member;
261                     member["@odata.id"] = boost::urls::format(
262                         "/redfish/v1/EventService/Subscriptions/{}" + id);
263                     memberArray.emplace_back(std::move(member));
264                 }
265                 crow::connections::systemBus->async_method_call(
266                     [asyncResp](const boost::system::error_code& ec,
267                                 const dbus::utility::ManagedObjectType& resp) {
268                         doSubscriptionCollection(ec, asyncResp, resp);
269                     },
270                     "xyz.openbmc_project.Network.SNMP",
271                     "/xyz/openbmc_project/network/snmp/manager",
272                     "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
273             });
274 
275     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
276         .privileges(redfish::privileges::postEventDestinationCollection)
277         .methods(
278             boost::beast::http::verb::
279                 post)([&app](
280                           const crow::Request& req,
281                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
282             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
283             {
284                 return;
285             }
286             if (EventServiceManager::getInstance().getNumberOfSubscriptions() >=
287                 maxNoOfSubscriptions)
288             {
289                 messages::eventSubscriptionLimitExceeded(asyncResp->res);
290                 return;
291             }
292             std::string destUrl;
293             std::string protocol;
294             std::optional<bool> verifyCertificate;
295             std::optional<std::string> context;
296             std::optional<std::string> subscriptionType;
297             std::optional<std::string> eventFormatType2;
298             std::optional<std::string> retryPolicy;
299             std::optional<std::vector<std::string>> msgIds;
300             std::optional<std::vector<std::string>> regPrefixes;
301             std::optional<std::vector<std::string>> originResources;
302             std::optional<std::vector<std::string>> resTypes;
303             std::optional<std::vector<nlohmann::json::object_t>> headers;
304             std::optional<std::vector<nlohmann::json::object_t>> mrdJsonArray;
305 
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, //
760                         "Context", context, //
761                         "DeliveryRetryPolicy", retryPolicy, //
762                         "HttpHeaders", headers, //
763                         "VerifyCertificate", verifyCertificate //
764                         ))
765                 {
766                     return;
767                 }
768 
769                 if (context)
770                 {
771                     subValue->userSub.customText = *context;
772                 }
773 
774                 if (headers)
775                 {
776                     boost::beast::http::fields fields;
777                     for (const nlohmann::json::object_t& headerChunk : *headers)
778                     {
779                         for (const auto& it : headerChunk)
780                         {
781                             const std::string* value =
782                                 it.second.get_ptr<const std::string*>();
783                             if (value == nullptr)
784                             {
785                                 messages::propertyValueFormatError(
786                                     asyncResp->res, it.second,
787                                     "HttpHeaders/" + it.first);
788                                 return;
789                             }
790                             fields.set(it.first, *value);
791                         }
792                     }
793                     subValue->userSub.httpHeaders = std::move(fields);
794                 }
795 
796                 if (retryPolicy)
797                 {
798                     if (std::ranges::find(supportedRetryPolicies,
799                                           *retryPolicy) ==
800                         supportedRetryPolicies.end())
801                     {
802                         messages::propertyValueNotInList(asyncResp->res,
803                                                          *retryPolicy,
804                                                          "DeliveryRetryPolicy");
805                         return;
806                     }
807                     subValue->userSub.retryPolicy = *retryPolicy;
808                 }
809 
810                 if (verifyCertificate)
811                 {
812                     subValue->userSub.verifyCertificate = *verifyCertificate;
813                 }
814 
815                 EventServiceManager::getInstance().updateSubscriptionData();
816             });
817     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
818         // The below privilege is wrong, it should be ConfigureManager OR
819         // ConfigureSelf
820         // https://github.com/openbmc/bmcweb/issues/220
821         //.privileges(redfish::privileges::deleteEventDestination)
822         .privileges({{"ConfigureManager"}})
823         .methods(boost::beast::http::verb::delete_)(
824             [&app](const crow::Request& req,
825                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
826                    const std::string& param) {
827                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
828                 {
829                     return;
830                 }
831                 EventServiceManager& event = EventServiceManager::getInstance();
832                 if (param.starts_with("snmp"))
833                 {
834                     deleteSnmpTrapClient(asyncResp, param);
835                     event.deleteSubscription(param);
836                     return;
837                 }
838 
839                 if (!event.deleteSubscription(param))
840                 {
841                     messages::resourceNotFound(asyncResp->res,
842                                                "EventDestination", param);
843                     return;
844                 }
845                 messages::success(asyncResp->res);
846             });
847 }
848 
849 } // namespace redfish
850