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