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>(
436                     persistent_data::UserSubscription{}, *url, app.ioContext());
437 
438             subValue->userSub.destinationUrl = std::move(*url);
439 
440             if (subscriptionType)
441             {
442                 if (*subscriptionType != "RedfishEvent")
443                 {
444                     messages::propertyValueNotInList(
445                         asyncResp->res, *subscriptionType, "SubscriptionType");
446                     return;
447                 }
448                 subValue->userSub.subscriptionType = *subscriptionType;
449             }
450             else
451             {
452                 // Default
453                 subValue->userSub.subscriptionType = "RedfishEvent";
454             }
455 
456             if (protocol != "Redfish")
457             {
458                 messages::propertyValueNotInList(asyncResp->res, protocol,
459                                                  "Protocol");
460                 return;
461             }
462             subValue->userSub.protocol = protocol;
463 
464             if (verifyCertificate)
465             {
466                 subValue->userSub.verifyCertificate = *verifyCertificate;
467             }
468 
469             if (eventFormatType2)
470             {
471                 if (std::ranges::find(supportedEvtFormatTypes,
472                                       *eventFormatType2) ==
473                     supportedEvtFormatTypes.end())
474                 {
475                     messages::propertyValueNotInList(
476                         asyncResp->res, *eventFormatType2, "EventFormatType");
477                     return;
478                 }
479                 subValue->userSub.eventFormatType = *eventFormatType2;
480             }
481             else
482             {
483                 // If not specified, use default "Event"
484                 subValue->userSub.eventFormatType = "Event";
485             }
486 
487             if (context)
488             {
489                 // This value is selected arbitrarily.
490                 constexpr const size_t maxContextSize = 256;
491                 if (context->size() > maxContextSize)
492                 {
493                     messages::stringValueTooLong(asyncResp->res, "Context",
494                                                  maxContextSize);
495                     return;
496                 }
497                 subValue->userSub.customText = *context;
498             }
499 
500             if (headers)
501             {
502                 size_t cumulativeLen = 0;
503 
504                 for (const nlohmann::json::object_t& headerChunk : *headers)
505                 {
506                     for (const auto& item : headerChunk)
507                     {
508                         const std::string* value =
509                             item.second.get_ptr<const std::string*>();
510                         if (value == nullptr)
511                         {
512                             messages::propertyValueFormatError(
513                                 asyncResp->res, item.second,
514                                 "HttpHeaders/" + item.first);
515                             return;
516                         }
517                         // Adding a new json value is the size of the key, +
518                         // the size of the value + 2 * 2 quotes for each, +
519                         // the colon and space between. example:
520                         // "key": "value"
521                         cumulativeLen += item.first.size() + value->size() + 6;
522                         // This value is selected to mirror http_connection.hpp
523                         constexpr const uint16_t maxHeaderSizeED = 8096;
524                         if (cumulativeLen > maxHeaderSizeED)
525                         {
526                             messages::arraySizeTooLong(
527                                 asyncResp->res, "HttpHeaders", maxHeaderSizeED);
528                             return;
529                         }
530                         subValue->userSub.httpHeaders.set(item.first, *value);
531                     }
532                 }
533             }
534 
535             if (regPrefixes)
536             {
537                 for (const std::string& it : *regPrefixes)
538                 {
539                     if (std::ranges::find(supportedRegPrefixes, it) ==
540                         supportedRegPrefixes.end())
541                     {
542                         messages::propertyValueNotInList(asyncResp->res, it,
543                                                          "RegistryPrefixes");
544                         return;
545                     }
546                 }
547                 subValue->userSub.registryPrefixes = *regPrefixes;
548             }
549 
550             if (originResources)
551             {
552                 subValue->userSub.originResources = *originResources;
553             }
554 
555             if (resTypes)
556             {
557                 for (const std::string& it : *resTypes)
558                 {
559                     if (std::ranges::find(supportedResourceTypes, it) ==
560                         supportedResourceTypes.end())
561                     {
562                         messages::propertyValueNotInList(asyncResp->res, it,
563                                                          "ResourceTypes");
564                         return;
565                     }
566                 }
567                 subValue->userSub.resourceTypes = *resTypes;
568             }
569 
570             if (msgIds)
571             {
572                 std::vector<std::string> registryPrefix;
573 
574                 // If no registry prefixes are mentioned, consider all
575                 // supported prefixes
576                 if (subValue->userSub.registryPrefixes.empty())
577                 {
578                     registryPrefix.assign(supportedRegPrefixes.begin(),
579                                           supportedRegPrefixes.end());
580                 }
581                 else
582                 {
583                     registryPrefix = subValue->userSub.registryPrefixes;
584                 }
585 
586                 for (const std::string& id : *msgIds)
587                 {
588                     bool validId = false;
589 
590                     // Check for Message ID in each of the selected Registry
591                     for (const std::string& it : registryPrefix)
592                     {
593                         const std::span<const redfish::registries::MessageEntry>
594                             registry =
595                                 redfish::registries::getRegistryFromPrefix(it);
596 
597                         if (std::ranges::any_of(
598                                 registry,
599                                 [&id](const redfish::registries::MessageEntry&
600                                           messageEntry) {
601                                     return id == messageEntry.first;
602                                 }))
603                         {
604                             validId = true;
605                             break;
606                         }
607                     }
608 
609                     if (!validId)
610                     {
611                         messages::propertyValueNotInList(asyncResp->res, id,
612                                                          "MessageIds");
613                         return;
614                     }
615                 }
616 
617                 subValue->userSub.registryMsgIds = *msgIds;
618             }
619 
620             if (retryPolicy)
621             {
622                 if (std::ranges::find(supportedRetryPolicies, *retryPolicy) ==
623                     supportedRetryPolicies.end())
624                 {
625                     messages::propertyValueNotInList(
626                         asyncResp->res, *retryPolicy, "DeliveryRetryPolicy");
627                     return;
628                 }
629                 subValue->userSub.retryPolicy = *retryPolicy;
630             }
631             else
632             {
633                 // Default "TerminateAfterRetries"
634                 subValue->userSub.retryPolicy = "TerminateAfterRetries";
635             }
636 
637             if (mrdJsonArray)
638             {
639                 for (nlohmann::json::object_t& mrdObj : *mrdJsonArray)
640                 {
641                     std::string mrdUri;
642 
643                     if (!json_util::readJsonObject(mrdObj, asyncResp->res,
644                                                    "@odata.id", mrdUri))
645 
646                     {
647                         return;
648                     }
649                     subValue->userSub.metricReportDefinitions.emplace_back(
650                         mrdUri);
651                 }
652             }
653 
654             std::string id =
655                 EventServiceManager::getInstance().addPushSubscription(
656                     subValue);
657             if (id.empty())
658             {
659                 messages::internalError(asyncResp->res);
660                 return;
661             }
662 
663             messages::created(asyncResp->res);
664             asyncResp->res.addHeader(
665                 "Location", "/redfish/v1/EventService/Subscriptions/" + id);
666         });
667 }
668 
669 inline void requestRoutesEventDestination(App& app)
670 {
671     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
672         .privileges(redfish::privileges::getEventDestination)
673         .methods(boost::beast::http::verb::get)(
674             [&app](const crow::Request& req,
675                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
676                    const std::string& param) {
677                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
678                 {
679                     return;
680                 }
681 
682                 if (param.starts_with("snmp"))
683                 {
684                     getSnmpTrapClient(asyncResp, param);
685                     return;
686                 }
687 
688                 std::shared_ptr<Subscription> subValue =
689                     EventServiceManager::getInstance().getSubscription(param);
690                 if (subValue == nullptr)
691                 {
692                     asyncResp->res.result(
693                         boost::beast::http::status::not_found);
694                     return;
695                 }
696                 const std::string& id = param;
697 
698                 const persistent_data::UserSubscription& userSub =
699                     subValue->userSub;
700 
701                 nlohmann::json& jVal = asyncResp->res.jsonValue;
702                 jVal["@odata.type"] =
703                     "#EventDestination.v1_14_1.EventDestination";
704                 jVal["Protocol"] =
705                     event_destination::EventDestinationProtocol::Redfish;
706                 jVal["@odata.id"] = boost::urls::format(
707                     "/redfish/v1/EventService/Subscriptions/{}", id);
708                 jVal["Id"] = id;
709                 jVal["Name"] = "Event Destination " + id;
710                 jVal["Destination"] = userSub.destinationUrl;
711                 jVal["Context"] = userSub.customText;
712                 jVal["SubscriptionType"] = userSub.subscriptionType;
713                 jVal["HttpHeaders"] = nlohmann::json::array();
714                 jVal["EventFormatType"] = userSub.eventFormatType;
715                 jVal["RegistryPrefixes"] = userSub.registryPrefixes;
716                 jVal["ResourceTypes"] = userSub.resourceTypes;
717 
718                 jVal["MessageIds"] = userSub.registryMsgIds;
719                 jVal["DeliveryRetryPolicy"] = userSub.retryPolicy;
720                 jVal["VerifyCertificate"] = userSub.verifyCertificate;
721 
722                 nlohmann::json::array_t mrdJsonArray;
723                 for (const auto& mdrUri : userSub.metricReportDefinitions)
724                 {
725                     nlohmann::json::object_t mdr;
726                     mdr["@odata.id"] = mdrUri;
727                     mrdJsonArray.emplace_back(std::move(mdr));
728                 }
729                 jVal["MetricReportDefinitions"] = mrdJsonArray;
730             });
731     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
732         // The below privilege is wrong, it should be ConfigureManager OR
733         // ConfigureSelf
734         // https://github.com/openbmc/bmcweb/issues/220
735         //.privileges(redfish::privileges::patchEventDestination)
736         .privileges({{"ConfigureManager"}})
737         .methods(boost::beast::http::verb::patch)(
738             [&app](const crow::Request& req,
739                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
740                    const std::string& param) {
741                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
742                 {
743                     return;
744                 }
745                 std::shared_ptr<Subscription> subValue =
746                     EventServiceManager::getInstance().getSubscription(param);
747                 if (subValue == nullptr)
748                 {
749                     asyncResp->res.result(
750                         boost::beast::http::status::not_found);
751                     return;
752                 }
753 
754                 std::optional<std::string> context;
755                 std::optional<std::string> retryPolicy;
756                 std::optional<bool> verifyCertificate;
757                 std::optional<std::vector<nlohmann::json::object_t>> headers;
758 
759                 if (!json_util::readJsonPatch( //
760                         req, asyncResp->res, //
761                         "Context", context, //
762                         "DeliveryRetryPolicy", retryPolicy, //
763                         "HttpHeaders", headers, //
764                         "VerifyCertificate", verifyCertificate //
765                         ))
766                 {
767                     return;
768                 }
769 
770                 if (context)
771                 {
772                     subValue->userSub.customText = *context;
773                 }
774 
775                 if (headers)
776                 {
777                     boost::beast::http::fields fields;
778                     for (const nlohmann::json::object_t& headerChunk : *headers)
779                     {
780                         for (const auto& it : headerChunk)
781                         {
782                             const std::string* value =
783                                 it.second.get_ptr<const std::string*>();
784                             if (value == nullptr)
785                             {
786                                 messages::propertyValueFormatError(
787                                     asyncResp->res, it.second,
788                                     "HttpHeaders/" + it.first);
789                                 return;
790                             }
791                             fields.set(it.first, *value);
792                         }
793                     }
794                     subValue->userSub.httpHeaders = std::move(fields);
795                 }
796 
797                 if (retryPolicy)
798                 {
799                     if (std::ranges::find(supportedRetryPolicies,
800                                           *retryPolicy) ==
801                         supportedRetryPolicies.end())
802                     {
803                         messages::propertyValueNotInList(asyncResp->res,
804                                                          *retryPolicy,
805                                                          "DeliveryRetryPolicy");
806                         return;
807                     }
808                     subValue->userSub.retryPolicy = *retryPolicy;
809                 }
810 
811                 if (verifyCertificate)
812                 {
813                     subValue->userSub.verifyCertificate = *verifyCertificate;
814                 }
815 
816                 // Sync Subscription to UserSubscriptionConfig
817                 persistent_data::EventServiceStore::getInstance()
818                     .updateUserSubscriptionConfig(subValue->userSub);
819 
820                 EventServiceManager::getInstance().updateSubscriptionData();
821             });
822     BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
823         // The below privilege is wrong, it should be ConfigureManager OR
824         // ConfigureSelf
825         // https://github.com/openbmc/bmcweb/issues/220
826         //.privileges(redfish::privileges::deleteEventDestination)
827         .privileges({{"ConfigureManager"}})
828         .methods(boost::beast::http::verb::delete_)(
829             [&app](const crow::Request& req,
830                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
831                    const std::string& param) {
832                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
833                 {
834                     return;
835                 }
836                 EventServiceManager& event = EventServiceManager::getInstance();
837                 if (param.starts_with("snmp"))
838                 {
839                     deleteSnmpTrapClient(asyncResp, param);
840                     event.deleteSubscription(param);
841                     return;
842                 }
843 
844                 if (!event.deleteSubscription(param))
845                 {
846                     messages::resourceNotFound(asyncResp->res,
847                                                "EventDestination", param);
848                     return;
849                 }
850                 messages::success(asyncResp->res);
851             });
852 }
853 
854 } // namespace redfish
855