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     if (!json_util::readJsonPatch(
511             req, asyncResp->res, //
512             "HostName", newHostName, //
513             "NTP/NTPServers", ntpServerObjects, //
514             "NTP/ProtocolEnabled", ntpEnabled, //
515             "IPMI/ProtocolEnabled", ipmiEnabled, //
516             "SSH/ProtocolEnabled", sshEnabled //
517             ))
518     {
519         return;
520     }
521 
522     asyncResp->res.result(boost::beast::http::status::no_content);
523     if (newHostName)
524     {
525         messages::propertyNotWritable(asyncResp->res, "HostName");
526         return;
527     }
528 
529     if (ntpEnabled)
530     {
531         handleNTPProtocolEnabled(asyncResp, *ntpEnabled);
532     }
533     if (ntpServerObjects)
534     {
535         getEthernetIfaceData(
536             [asyncResp, ntpServerObjects](
537                 const bool success, std::vector<std::string>& currentNtpServers,
538                 const std::vector<std::string>& /*dynamicNtpServers*/,
539                 const std::vector<std::string>& /*domainNames*/) {
540                 if (!success)
541                 {
542                     messages::internalError(asyncResp->res);
543                     return;
544                 }
545                 handleNTPServersPatch(asyncResp, *ntpServerObjects,
546                                       std::move(currentNtpServers));
547             });
548     }
549 
550     if (ipmiEnabled)
551     {
552         handleProtocolEnabled(
553             *ipmiEnabled, asyncResp,
554             encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
555     }
556 
557     if (sshEnabled)
558     {
559         handleProtocolEnabled(*sshEnabled, asyncResp,
560                               encodeServiceObjectPath(sshServiceName));
561     }
562 }
563 
handleManagersNetworkProtocolHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)564 inline void handleManagersNetworkProtocolHead(
565     App& app, const crow::Request& req,
566     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
567     const std::string& managerId)
568 {
569     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
570     {
571         return;
572     }
573     asyncResp->res.addHeader(
574         boost::beast::http::field::link,
575         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
576     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
577     {
578         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
579         return;
580     }
581 }
582 
handleManagersNetworkProtocolGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)583 inline void handleManagersNetworkProtocolGet(
584     App& app, const crow::Request& req,
585     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
586     const std::string& managerId)
587 {
588     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
589     {
590         return;
591     }
592     asyncResp->res.addHeader(
593         boost::beast::http::field::link,
594         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
595     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
596     {
597         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
598         return;
599     }
600 
601     getNetworkData(asyncResp, req);
602 }
603 
requestRoutesNetworkProtocol(App & app)604 inline void requestRoutesNetworkProtocol(App& app)
605 {
606     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
607         .privileges(redfish::privileges::patchManagerNetworkProtocol)
608         .methods(boost::beast::http::verb::patch)(
609             std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
610 
611     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
612         .privileges(redfish::privileges::headManagerNetworkProtocol)
613         .methods(boost::beast::http::verb::head)(
614             std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
615 
616     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
617         .privileges(redfish::privileges::getManagerNetworkProtocol)
618         .methods(boost::beast::http::verb::get)(
619             std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
620 }
621 
622 } // namespace redfish
623