1 /*
2 Copyright (c) 2018 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 
18 #include "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "error_messages.hpp"
21 #include "generated/enums/resource.hpp"
22 #include "query.hpp"
23 #include "redfish_util.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "utils/json_utils.hpp"
26 #include "utils/stl_utils.hpp"
27 
28 #include <boost/system/error_code.hpp>
29 #include <boost/url/format.hpp>
30 #include <sdbusplus/asio/property.hpp>
31 
32 #include <array>
33 #include <optional>
34 #include <string_view>
35 #include <variant>
36 #include <vector>
37 
38 namespace redfish
39 {
40 
41 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
42 std::string getHostName();
43 
44 static constexpr std::string_view sshServiceName = "dropbear";
45 static constexpr std::string_view httpsServiceName = "bmcweb";
46 static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net";
47 
48 // Mapping from Redfish NetworkProtocol key name to backend service that hosts
49 // that protocol.
50 static constexpr std::array<std::pair<std::string_view, std::string_view>, 3>
51     networkProtocolToDbus = {{{"SSH", sshServiceName},
52                               {"HTTPS", httpsServiceName},
53                               {"IPMI", ipmiServiceName}}};
54 
extractNTPServersAndDomainNamesData(const dbus::utility::ManagedObjectType & dbusData,std::vector<std::string> & ntpData,std::vector<std::string> & dynamicNtpData,std::vector<std::string> & dnData)55 inline void extractNTPServersAndDomainNamesData(
56     const dbus::utility::ManagedObjectType& dbusData,
57     std::vector<std::string>& ntpData, std::vector<std::string>& dynamicNtpData,
58     std::vector<std::string>& dnData)
59 {
60     for (const auto& obj : dbusData)
61     {
62         for (const auto& ifacePair : obj.second)
63         {
64             if (ifacePair.first !=
65                 "xyz.openbmc_project.Network.EthernetInterface")
66             {
67                 continue;
68             }
69 
70             for (const auto& propertyPair : ifacePair.second)
71             {
72                 if (propertyPair.first == "StaticNTPServers")
73                 {
74                     const std::vector<std::string>* ntpServers =
75                         std::get_if<std::vector<std::string>>(
76                             &propertyPair.second);
77                     if (ntpServers != nullptr)
78                     {
79                         ntpData.insert(ntpData.end(), ntpServers->begin(),
80                                        ntpServers->end());
81                     }
82                 }
83                 else if (propertyPair.first == "NTPServers")
84                 {
85                     const std::vector<std::string>* dynamicNtpServers =
86                         std::get_if<std::vector<std::string>>(
87                             &propertyPair.second);
88                     if (dynamicNtpServers != nullptr)
89                     {
90                         dynamicNtpData = *dynamicNtpServers;
91                     }
92                 }
93                 else if (propertyPair.first == "DomainName")
94                 {
95                     const std::vector<std::string>* domainNames =
96                         std::get_if<std::vector<std::string>>(
97                             &propertyPair.second);
98                     if (domainNames != nullptr)
99                     {
100                         dnData.insert(dnData.end(), domainNames->begin(),
101                                       domainNames->end());
102                     }
103                 }
104             }
105         }
106     }
107     stl_utils::removeDuplicate(ntpData);
108     stl_utils::removeDuplicate(dnData);
109 }
110 
111 template <typename CallbackFunc>
getEthernetIfaceData(CallbackFunc && callback)112 void getEthernetIfaceData(CallbackFunc&& callback)
113 {
114     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
115     dbus::utility::getManagedObjects(
116         "xyz.openbmc_project.Network", path,
117         [callback = std::forward<CallbackFunc>(callback)](
118             const boost::system::error_code& ec,
119             const dbus::utility::ManagedObjectType& dbusData) {
120             std::vector<std::string> ntpServers;
121             std::vector<std::string> dynamicNtpServers;
122             std::vector<std::string> domainNames;
123 
124             if (ec)
125             {
126                 callback(false, ntpServers, dynamicNtpServers, domainNames);
127                 return;
128             }
129 
130             extractNTPServersAndDomainNamesData(dbusData, ntpServers,
131                                                 dynamicNtpServers, domainNames);
132 
133             callback(true, ntpServers, dynamicNtpServers, domainNames);
134         });
135 }
136 
afterNetworkPortRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const std::vector<std::tuple<std::string,std::string,bool>> & socketData)137 inline void afterNetworkPortRequest(
138     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
139     const boost::system::error_code& ec,
140     const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
141 {
142     if (ec)
143     {
144         messages::internalError(asyncResp->res);
145         return;
146     }
147     for (const auto& data : socketData)
148     {
149         const std::string& socketPath = get<0>(data);
150         const std::string& protocolName = get<1>(data);
151         bool isProtocolEnabled = get<2>(data);
152 
153         asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
154             isProtocolEnabled;
155         asyncResp->res.jsonValue[protocolName]["Port"] = nullptr;
156         getPortNumber(socketPath, [asyncResp, protocolName](
157                                       const boost::system::error_code& ec2,
158                                       int portNumber) {
159             if (ec2)
160             {
161                 messages::internalError(asyncResp->res);
162                 return;
163             }
164             asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
165         });
166     }
167 }
168 
getNetworkData(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)169 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
170                            const crow::Request& req)
171 {
172     if (req.session == nullptr)
173     {
174         messages::internalError(asyncResp->res);
175         return;
176     }
177 
178     asyncResp->res.addHeader(
179         boost::beast::http::field::link,
180         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby");
181     asyncResp->res.jsonValue["@odata.type"] =
182         "#ManagerNetworkProtocol.v1_9_0.ManagerNetworkProtocol";
183     asyncResp->res.jsonValue["@odata.id"] =
184         boost::urls::format("/redfish/v1/Managers/{}/NetworkProtocol",
185                             BMCWEB_REDFISH_MANAGER_URI_NAME);
186     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
187     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
188     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
189     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
190     asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK;
191     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
192 
193     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
194     // but from security perspective it is not recommended to use.
195     // Hence using protocolEnabled as false to make it OCP and security-wise
196     // compliant
197     asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
198     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
199 
200     // The ProtocolEnabled of the following protocols is determined by
201     // inspecting the state of associated systemd sockets. If these protocols
202     // have been disabled, then the systemd socket unit files will not be found
203     // and the protocols will not be returned in this Redfish query. Set some
204     // defaults to ensure something is always returned.
205     for (const auto& nwkProtocol : networkProtocolToDbus)
206     {
207         asyncResp->res.jsonValue[nwkProtocol.first]["Port"] = nullptr;
208         asyncResp->res.jsonValue[nwkProtocol.first]["ProtocolEnabled"] = false;
209     }
210 
211     std::string hostName = getHostName();
212 
213     asyncResp->res.jsonValue["HostName"] = hostName;
214 
215     getNTPProtocolEnabled(asyncResp);
216 
217     getEthernetIfaceData([hostName, asyncResp](
218                              const bool& success,
219                              const std::vector<std::string>& ntpServers,
220                              const std::vector<std::string>& dynamicNtpServers,
221                              const std::vector<std::string>& domainNames) {
222         if (!success)
223         {
224             messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
225                                        "NetworkProtocol");
226             return;
227         }
228         asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
229         asyncResp->res.jsonValue["NTP"]["NetworkSuppliedServers"] =
230             dynamicNtpServers;
231         if (!hostName.empty())
232         {
233             std::string fqdn = hostName;
234             if (!domainNames.empty())
235             {
236                 fqdn += ".";
237                 fqdn += domainNames[0];
238             }
239             asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
240         }
241     });
242 
243     Privileges effectiveUserPrivileges =
244         redfish::getUserPrivileges(*req.session);
245 
246     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
247     // something only ConfigureManager can access then only display when
248     // the user has permissions ConfigureManager
249     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
250                                          effectiveUserPrivileges))
251     {
252         asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
253             boost::urls::format(
254                 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
255                 BMCWEB_REDFISH_MANAGER_URI_NAME);
256     }
257 
258     getPortStatusAndPath(std::span(networkProtocolToDbus),
259                          std::bind_front(afterNetworkPortRequest, asyncResp));
260 } // namespace redfish
261 
afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)262 inline void afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
263                         const boost::system::error_code& ec)
264 {
265     if (ec)
266     {
267         BMCWEB_LOG_DEBUG("Failed to set elapsed time. DBUS response error {}",
268                          ec);
269         messages::internalError(asyncResp->res);
270         return;
271     }
272     asyncResp->res.result(boost::beast::http::status::no_content);
273 }
274 
handleNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,bool ntpEnabled)275 inline void handleNTPProtocolEnabled(
276     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, bool ntpEnabled)
277 {
278     bool interactive = false;
279     auto callback = [asyncResp](const boost::system::error_code& ec) {
280         afterSetNTP(asyncResp, ec);
281     };
282     crow::connections::systemBus->async_method_call(
283         std::move(callback), "org.freedesktop.timedate1",
284         "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "SetNTP",
285         ntpEnabled, interactive);
286 }
287 
288 // Redfish states that ip addresses can be
289 // string, to set a value
290 // null, to delete the value
291 // object_t, empty json object, to ignore the value
292 using IpAddress =
293     std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>;
294 
295 inline void
handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::vector<IpAddress> & ntpServerObjects,std::vector<std::string> currentNtpServers)296     handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
297                           const std::vector<IpAddress>& ntpServerObjects,
298                           std::vector<std::string> currentNtpServers)
299 {
300     std::vector<std::string>::iterator currentNtpServer =
301         currentNtpServers.begin();
302     for (size_t index = 0; index < ntpServerObjects.size(); index++)
303     {
304         const IpAddress& ntpServer = ntpServerObjects[index];
305         if (std::holds_alternative<std::nullptr_t>(ntpServer))
306         {
307             // Can't delete an item that doesn't exist
308             if (currentNtpServer == currentNtpServers.end())
309             {
310                 messages::propertyValueNotInList(
311                     asyncResp->res, "null",
312                     "NTP/NTPServers/" + std::to_string(index));
313 
314                 return;
315             }
316             currentNtpServer = currentNtpServers.erase(currentNtpServer);
317             continue;
318         }
319         const nlohmann::json::object_t* ntpServerObject =
320             std::get_if<nlohmann::json::object_t>(&ntpServer);
321         if (ntpServerObject != nullptr)
322         {
323             if (!ntpServerObject->empty())
324             {
325                 messages::propertyValueNotInList(
326                     asyncResp->res, *ntpServerObject,
327                     "NTP/NTPServers/" + std::to_string(index));
328                 return;
329             }
330             // Can't retain an item that doesn't exist
331             if (currentNtpServer == currentNtpServers.end())
332             {
333                 messages::propertyValueOutOfRange(
334                     asyncResp->res, *ntpServerObject,
335                     "NTP/NTPServers/" + std::to_string(index));
336 
337                 return;
338             }
339             // empty objects should leave the NtpServer unmodified
340             currentNtpServer++;
341             continue;
342         }
343 
344         const std::string* ntpServerStr = std::get_if<std::string>(&ntpServer);
345         if (ntpServerStr == nullptr)
346         {
347             messages::internalError(asyncResp->res);
348             return;
349         }
350         if (currentNtpServer == currentNtpServers.end())
351         {
352             // if we're at the end of the list, append to the end
353             currentNtpServers.push_back(*ntpServerStr);
354             currentNtpServer = currentNtpServers.end();
355             continue;
356         }
357         *currentNtpServer = *ntpServerStr;
358         currentNtpServer++;
359     }
360 
361     // Any remaining array elements should be removed
362     currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
363 
364     constexpr std::array<std::string_view, 1> ethInterfaces = {
365         "xyz.openbmc_project.Network.EthernetInterface"};
366     dbus::utility::getSubTree(
367         "/xyz/openbmc_project", 0, ethInterfaces,
368         [asyncResp, currentNtpServers](
369             const boost::system::error_code& ec,
370             const dbus::utility::MapperGetSubTreeResponse& subtree) {
371             if (ec)
372             {
373                 BMCWEB_LOG_WARNING("D-Bus error: {}, {}", ec, ec.message());
374                 messages::internalError(asyncResp->res);
375                 return;
376             }
377 
378             for (const auto& [objectPath, serviceMap] : subtree)
379             {
380                 for (const auto& [service, interfaces] : serviceMap)
381                 {
382                     for (const auto& interface : interfaces)
383                     {
384                         if (interface !=
385                             "xyz.openbmc_project.Network.EthernetInterface")
386                         {
387                             continue;
388                         }
389 
390                         setDbusProperty(asyncResp, "NTP/NTPServers/", service,
391                                         objectPath, interface,
392                                         "StaticNTPServers", currentNtpServers);
393                     }
394                 }
395             }
396         });
397 }
398 
399 inline void
handleProtocolEnabled(const bool protocolEnabled,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & netBasePath)400     handleProtocolEnabled(const bool protocolEnabled,
401                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
402                           const std::string& netBasePath)
403 {
404     constexpr std::array<std::string_view, 1> interfaces = {
405         "xyz.openbmc_project.Control.Service.Attributes"};
406     dbus::utility::getSubTree(
407         "/xyz/openbmc_project/control/service", 0, interfaces,
408         [protocolEnabled, asyncResp,
409          netBasePath](const boost::system::error_code& ec,
410                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
411             if (ec)
412             {
413                 messages::internalError(asyncResp->res);
414                 return;
415             }
416 
417             for (const auto& entry : subtree)
418             {
419                 if (entry.first.starts_with(netBasePath))
420                 {
421                     setDbusProperty(
422                         asyncResp, "IPMI/ProtocolEnabled",
423                         entry.second.begin()->first, entry.first,
424                         "xyz.openbmc_project.Control.Service.Attributes",
425                         "Running", protocolEnabled);
426                     setDbusProperty(
427                         asyncResp, "IPMI/ProtocolEnabled",
428                         entry.second.begin()->first, entry.first,
429                         "xyz.openbmc_project.Control.Service.Attributes",
430                         "Enabled", protocolEnabled);
431                 }
432             }
433         });
434 }
435 
getHostName()436 inline std::string getHostName()
437 {
438     std::string hostName;
439 
440     std::array<char, HOST_NAME_MAX> hostNameCStr{};
441     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
442     {
443         hostName = hostNameCStr.data();
444     }
445     return hostName;
446 }
447 
448 inline void
getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)449     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
450 {
451     sdbusplus::asio::getProperty<bool>(
452         *crow::connections::systemBus, "org.freedesktop.timedate1",
453         "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP",
454         [asyncResp](const boost::system::error_code& ec, bool enabled) {
455             if (ec)
456             {
457                 BMCWEB_LOG_WARNING(
458                     "Failed to get NTP status, assuming not supported");
459                 return;
460             }
461 
462             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = enabled;
463         });
464 }
465 
encodeServiceObjectPath(std::string_view serviceName)466 inline std::string encodeServiceObjectPath(std::string_view serviceName)
467 {
468     sdbusplus::message::object_path objPath(
469         "/xyz/openbmc_project/control/service");
470     objPath /= serviceName;
471     return objPath.str;
472 }
473 
handleBmcNetworkProtocolHead(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)474 inline void handleBmcNetworkProtocolHead(
475     crow::App& app, const crow::Request& req,
476     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
477 {
478     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
479     {
480         return;
481     }
482     asyncResp->res.addHeader(
483         boost::beast::http::field::link,
484         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
485 }
486 
handleManagersNetworkProtocolPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)487 inline void handleManagersNetworkProtocolPatch(
488     App& app, const crow::Request& req,
489     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
490     const std::string& managerId)
491 {
492     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
493     {
494         return;
495     }
496 
497     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
498     {
499         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
500         return;
501     }
502 
503     std::optional<std::string> newHostName;
504 
505     std::optional<std::vector<IpAddress>> ntpServerObjects;
506     std::optional<bool> ntpEnabled;
507     std::optional<bool> ipmiEnabled;
508     std::optional<bool> sshEnabled;
509 
510     // clang-format off
511         if (!json_util::readJsonPatch(
512                 req, asyncResp->res,
513                 "HostName", newHostName,
514                 "NTP/NTPServers", ntpServerObjects,
515                 "NTP/ProtocolEnabled", ntpEnabled,
516                 "IPMI/ProtocolEnabled", ipmiEnabled,
517                 "SSH/ProtocolEnabled", sshEnabled))
518         {
519             return;
520         }
521     // clang-format on
522 
523     asyncResp->res.result(boost::beast::http::status::no_content);
524     if (newHostName)
525     {
526         messages::propertyNotWritable(asyncResp->res, "HostName");
527         return;
528     }
529 
530     if (ntpEnabled)
531     {
532         handleNTPProtocolEnabled(asyncResp, *ntpEnabled);
533     }
534     if (ntpServerObjects)
535     {
536         getEthernetIfaceData(
537             [asyncResp, ntpServerObjects](
538                 const bool success, std::vector<std::string>& currentNtpServers,
539                 const std::vector<std::string>& /*dynamicNtpServers*/,
540                 const std::vector<std::string>& /*domainNames*/) {
541                 if (!success)
542                 {
543                     messages::internalError(asyncResp->res);
544                     return;
545                 }
546                 handleNTPServersPatch(asyncResp, *ntpServerObjects,
547                                       std::move(currentNtpServers));
548             });
549     }
550 
551     if (ipmiEnabled)
552     {
553         handleProtocolEnabled(
554             *ipmiEnabled, asyncResp,
555             encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
556     }
557 
558     if (sshEnabled)
559     {
560         handleProtocolEnabled(*sshEnabled, asyncResp,
561                               encodeServiceObjectPath(sshServiceName));
562     }
563 }
564 
handleManagersNetworkProtocolHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)565 inline void handleManagersNetworkProtocolHead(
566     App& app, const crow::Request& req,
567     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
568     const std::string& managerId)
569 {
570     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
571     {
572         return;
573     }
574     asyncResp->res.addHeader(
575         boost::beast::http::field::link,
576         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
577     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
578     {
579         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
580         return;
581     }
582 }
583 
handleManagersNetworkProtocolGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)584 inline void handleManagersNetworkProtocolGet(
585     App& app, const crow::Request& req,
586     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
587     const std::string& managerId)
588 {
589     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
590     {
591         return;
592     }
593     asyncResp->res.addHeader(
594         boost::beast::http::field::link,
595         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
596     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
597     {
598         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
599         return;
600     }
601 
602     getNetworkData(asyncResp, req);
603 }
604 
requestRoutesNetworkProtocol(App & app)605 inline void requestRoutesNetworkProtocol(App& app)
606 {
607     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
608         .privileges(redfish::privileges::patchManagerNetworkProtocol)
609         .methods(boost::beast::http::verb::patch)(
610             std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
611 
612     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
613         .privileges(redfish::privileges::headManagerNetworkProtocol)
614         .methods(boost::beast::http::verb::head)(
615             std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
616 
617     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
618         .privileges(redfish::privileges::getManagerNetworkProtocol)
619         .methods(boost::beast::http::verb::get)(
620             std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
621 }
622 
623 } // namespace redfish
624