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