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