xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 8aae75ad51881046f2fab9ca64c7addfb004e06b)
1 /*
2 // Copyright (c) 2018 IBM 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 "node.hpp"
19 
20 #include <variant>
21 namespace redfish
22 {
23 namespace certs
24 {
25 constexpr char const *httpsObjectPath =
26     "/xyz/openbmc_project/certs/server/https";
27 constexpr char const *certInstallIntf = "xyz.openbmc_project.Certs.Install";
28 constexpr char const *certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
29 constexpr char const *certPropIntf = "xyz.openbmc_project.Certs.Certificate";
30 constexpr char const *dbusPropIntf = "org.freedesktop.DBus.Properties";
31 constexpr char const *dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
32 constexpr char const *mapperBusName = "xyz.openbmc_project.ObjectMapper";
33 constexpr char const *mapperObjectPath = "/xyz/openbmc_project/object_mapper";
34 constexpr char const *mapperIntf = "xyz.openbmc_project.ObjectMapper";
35 constexpr char const *ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap";
36 constexpr char const *httpsServiceName =
37     "xyz.openbmc_project.Certs.Manager.Server.Https";
38 constexpr char const *ldapServiceName =
39     "xyz.openbmc_project.Certs.Manager.Client.Ldap";
40 } // namespace certs
41 
42 /**
43  * The Certificate schema defines a Certificate Service which represents the
44  * actions available to manage certificates and links to where certificates
45  * are installed.
46  */
47 class CertificateService : public Node
48 {
49   public:
50     CertificateService(CrowApp &app) :
51         Node(app, "/redfish/v1/CertificateService/")
52     {
53         // TODO: Issue#61 No entries are available for Certificate
54         // sevice at https://www.dmtf.org/standards/redfish
55         // "redfish standard registries". Need to modify after DMTF
56         // publish Privilege details for certificate service
57         entityPrivileges = {
58             {boost::beast::http::verb::get, {{"Login"}}},
59             {boost::beast::http::verb::head, {{"Login"}}},
60             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
61             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
62             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
63             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
64     }
65 
66   private:
67     void doGet(crow::Response &res, const crow::Request &req,
68                const std::vector<std::string> &params) override
69     {
70         res.jsonValue = {
71             {"@odata.type", "#CertificateService.v1_0_0.CertificateService"},
72             {"@odata.id", "/redfish/v1/CertificateService"},
73             {"@odata.context",
74              "/redfish/v1/$metadata#CertificateService.CertificateService"},
75             {"Id", "CertificateService"},
76             {"Name", "Certificate Service"},
77             {"Description", "Actions available to manage certificates"}};
78         res.jsonValue["CertificateLocations"] = {
79             {"@odata.id",
80              "/redfish/v1/CertificateService/CertificateLocations"}};
81         res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = {
82             {"target", "/redfish/v1/CertificateService/Actions/"
83                        "CertificateService.ReplaceCertificate"},
84             {"CertificateType@Redfish.AllowableValues", {"PEM"}}};
85         res.end();
86     }
87 }; // CertificateService
88 
89 /**
90  * @brief Find the ID specified in the URL
91  * Finds the numbers specified after the last "/" in the URL and returns.
92  * @param[in] path URL
93  * @return -1 on failure and number on success
94  */
95 long getIDFromURL(const std::string_view url)
96 {
97     std::size_t found = url.rfind("/");
98     if (found == std::string::npos)
99     {
100         return -1;
101     }
102     if ((found + 1) < url.length())
103     {
104         char *endPtr;
105         std::string_view str = url.substr(found + 1);
106         long value = std::strtol(str.data(), &endPtr, 10);
107         if (endPtr != str.end())
108         {
109             return -1;
110         }
111         return value;
112     }
113     return -1;
114 }
115 
116 /**
117  * Class to create a temporary certificate file for uploading to system
118  */
119 class CertificateFile
120 {
121   public:
122     CertificateFile() = delete;
123     CertificateFile(const CertificateFile &) = delete;
124     CertificateFile &operator=(const CertificateFile &) = delete;
125     CertificateFile(CertificateFile &&) = delete;
126     CertificateFile &operator=(CertificateFile &&) = delete;
127     CertificateFile(const std::string &certString)
128     {
129         char dirTemplate[] = "/tmp/Certs.XXXXXX";
130         char *tempDirectory = mkdtemp(dirTemplate);
131         if (tempDirectory)
132         {
133             certDirectory = tempDirectory;
134             certificateFile = certDirectory / "cert.pem";
135             std::ofstream out(certificateFile, std::ofstream::out |
136                                                    std::ofstream::binary |
137                                                    std::ofstream::trunc);
138             out << certString;
139             out.close();
140             BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile;
141         }
142     }
143     ~CertificateFile()
144     {
145         if (std::filesystem::exists(certDirectory))
146         {
147             BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile;
148             try
149             {
150                 std::filesystem::remove_all(certDirectory);
151             }
152             catch (const std::filesystem::filesystem_error &e)
153             {
154                 BMCWEB_LOG_ERROR << "Failed to remove temp directory"
155                                  << certDirectory;
156             }
157         }
158     }
159     std::string getCertFilePath()
160     {
161         return certificateFile;
162     }
163 
164   private:
165     std::filesystem::path certificateFile;
166     std::filesystem::path certDirectory;
167 };
168 
169 /**
170  * @brief Parse and update Certficate Issue/Subject property
171  *
172  * @param[in] asyncResp Shared pointer to the response message
173  * @param[in] str  Issuer/Subject value in key=value pairs
174  * @param[in] type Issuer/Subject
175  * @return None
176  */
177 static void updateCertIssuerOrSubject(nlohmann::json &out,
178                                       const std::string_view value)
179 {
180     // example: O=openbmc-project.xyz,CN=localhost
181     std::string_view::iterator i = value.begin();
182     while (i != value.end())
183     {
184         std::string_view::iterator tokenBegin = i;
185         while (i != value.end() && *i != '=')
186         {
187             i++;
188         }
189         if (i == value.end())
190         {
191             break;
192         }
193         const std::string_view key(tokenBegin, i - tokenBegin);
194         i++;
195         tokenBegin = i;
196         while (i != value.end() && *i != ',')
197         {
198             i++;
199         }
200         const std::string_view val(tokenBegin, i - tokenBegin);
201         if (key == "L")
202         {
203             out["City"] = val;
204         }
205         else if (key == "CN")
206         {
207             out["CommonName"] = val;
208         }
209         else if (key == "C")
210         {
211             out["Country"] = val;
212         }
213         else if (key == "O")
214         {
215             out["Organization"] = val;
216         }
217         else if (key == "OU")
218         {
219             out["OrganizationalUnit"] = val;
220         }
221         else if (key == "ST")
222         {
223             out["State"] = val;
224         }
225         // skip comma character
226         if (i != value.end())
227         {
228             i++;
229         }
230     }
231 }
232 
233 /**
234  * @brief Retrieve the certificates properties and append to the response
235  * message
236  *
237  * @param[in] asyncResp Shared pointer to the response message
238  * @param[in] objectPath  Path of the D-Bus service object
239  * @param[in] certId  Id of the certificate
240  * @param[in] certURL  URL of the certificate object
241  * @param[in] name  name of the certificate
242  * @return None
243  */
244 static void getCertificateProperties(
245     const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath,
246     const std::string &service, long certId, const std::string &certURL,
247     const std::string &name)
248 {
249     using PropertyType =
250         std::variant<std::string, uint64_t, std::vector<std::string>>;
251     using PropertiesMap = boost::container::flat_map<std::string, PropertyType>;
252     BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
253                      << " certId=" << certId << " certURl=" << certURL;
254     crow::connections::systemBus->async_method_call(
255         [asyncResp, certURL, certId, name](const boost::system::error_code ec,
256                                            const PropertiesMap &properties) {
257             if (ec)
258             {
259                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
260                 messages::resourceNotFound(asyncResp->res, name,
261                                            std::to_string(certId));
262                 return;
263             }
264             asyncResp->res.jsonValue = {
265                 {"@odata.id", certURL},
266                 {"@odata.type", "#Certificate.v1_0_0.Certificate"},
267                 {"@odata.context",
268                  "/redfish/v1/$metadata#Certificate.Certificate"},
269                 {"Id", std::to_string(certId)},
270                 {"Name", name},
271                 {"Description", name}};
272             for (const auto &property : properties)
273             {
274                 if (property.first == "CertificateString")
275                 {
276                     asyncResp->res.jsonValue["CertificateString"] = "";
277                     const std::string *value =
278                         std::get_if<std::string>(&property.second);
279                     if (value)
280                     {
281                         asyncResp->res.jsonValue["CertificateString"] = *value;
282                     }
283                 }
284                 else if (property.first == "KeyUsage")
285                 {
286                     nlohmann::json &keyUsage =
287                         asyncResp->res.jsonValue["KeyUsage"];
288                     keyUsage = nlohmann::json::array();
289                     const std::vector<std::string> *value =
290                         std::get_if<std::vector<std::string>>(&property.second);
291                     if (value)
292                     {
293                         for (const std::string &usage : *value)
294                         {
295                             keyUsage.push_back(usage);
296                         }
297                     }
298                 }
299                 else if (property.first == "Issuer")
300                 {
301                     const std::string *value =
302                         std::get_if<std::string>(&property.second);
303                     if (value)
304                     {
305                         updateCertIssuerOrSubject(
306                             asyncResp->res.jsonValue["Issuer"], *value);
307                     }
308                 }
309                 else if (property.first == "Subject")
310                 {
311                     const std::string *value =
312                         std::get_if<std::string>(&property.second);
313                     if (value)
314                     {
315                         updateCertIssuerOrSubject(
316                             asyncResp->res.jsonValue["Subject"], *value);
317                     }
318                 }
319                 else if (property.first == "ValidNotAfter")
320                 {
321                     const uint64_t *value =
322                         std::get_if<uint64_t>(&property.second);
323                     if (value)
324                     {
325                         std::time_t time = static_cast<std::time_t>(*value);
326                         asyncResp->res.jsonValue["ValidNotAfter"] =
327                             crow::utility::getDateTime(time);
328                     }
329                 }
330                 else if (property.first == "ValidNotBefore")
331                 {
332                     const uint64_t *value =
333                         std::get_if<uint64_t>(&property.second);
334                     if (value)
335                     {
336                         std::time_t time = static_cast<std::time_t>(*value);
337                         asyncResp->res.jsonValue["ValidNotBefore"] =
338                             crow::utility::getDateTime(time);
339                     }
340                 }
341             }
342             asyncResp->res.addHeader("Location", certURL);
343         },
344         service, objectPath, certs::dbusPropIntf, "GetAll",
345         certs::certPropIntf);
346 }
347 
348 using GetObjectType =
349     std::vector<std::pair<std::string, std::vector<std::string>>>;
350 
351 /**
352  * Action to replace an existing certificate
353  */
354 class CertificateActionsReplaceCertificate : public Node
355 {
356   public:
357     CertificateActionsReplaceCertificate(CrowApp &app) :
358         Node(app, "/redfish/v1/CertificateService/Actions/"
359                   "CertificateService.ReplaceCertificate/")
360     {
361         entityPrivileges = {
362             {boost::beast::http::verb::get, {{"Login"}}},
363             {boost::beast::http::verb::head, {{"Login"}}},
364             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
365             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
366             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
367             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
368     }
369 
370   private:
371     void doPost(crow::Response &res, const crow::Request &req,
372                 const std::vector<std::string> &params) override
373     {
374         std::string certificate;
375         nlohmann::json certificateUri;
376         std::optional<std::string> certificateType = "PEM";
377         auto asyncResp = std::make_shared<AsyncResp>(res);
378         if (!json_util::readJson(req, asyncResp->res, "CertificateString",
379                                  certificate, "CertificateUri", certificateUri,
380                                  "CertificateType", certificateType))
381         {
382             BMCWEB_LOG_ERROR << "Required parameters are missing";
383             messages::internalError(asyncResp->res);
384             return;
385         }
386 
387         if (!certificateType)
388         {
389             // should never happen, but it never hurts to be paranoid.
390             return;
391         }
392         if (certificateType != "PEM")
393         {
394             messages::actionParameterNotSupported(
395                 asyncResp->res, "CertificateType", "ReplaceCertificate");
396             return;
397         }
398 
399         std::string certURI;
400         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
401                                           "@odata.id", certURI))
402         {
403             messages::actionParameterMissing(
404                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
405             return;
406         }
407 
408         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
409         long id = getIDFromURL(certURI);
410         if (id < 0)
411         {
412             messages::actionParameterValueFormatError(asyncResp->res, certURI,
413                                                       "CertificateUri",
414                                                       "ReplaceCertificate");
415             return;
416         }
417         std::string objectPath;
418         std::string name;
419         std::string service;
420         if (boost::starts_with(
421                 certURI,
422                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
423         {
424             objectPath =
425                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
426             name = "HTTPS certificate";
427             service = certs::httpsServiceName;
428         }
429         else if (boost::starts_with(
430                      certURI, "/redfish/v1/AccountService/LDAP/Certificates/"))
431         {
432             objectPath =
433                 std::string(certs::ldapObjectPath) + "/" + std::to_string(id);
434             name = "LDAP certificate";
435             service = certs::ldapServiceName;
436         }
437         else
438         {
439             messages::actionParameterNotSupported(
440                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
441             return;
442         }
443 
444         std::shared_ptr<CertificateFile> certFile =
445             std::make_shared<CertificateFile>(certificate);
446         crow::connections::systemBus->async_method_call(
447             [asyncResp, certFile, objectPath, service, certURI, id,
448              name](const boost::system::error_code ec) {
449                 if (ec)
450                 {
451                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
452                     messages::resourceNotFound(asyncResp->res, name,
453                                                std::to_string(id));
454                     return;
455                 }
456                 getCertificateProperties(asyncResp, objectPath, service, id,
457                                          certURI, name);
458                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
459                                  << certFile->getCertFilePath();
460             },
461             service, objectPath, certs::certReplaceIntf, "Replace",
462             certFile->getCertFilePath());
463     }
464 }; // CertificateActionsReplaceCertificate
465 
466 /**
467  * Certificate resource describes a certificate used to prove the identity
468  * of a component, account or service.
469  */
470 class HTTPSCertificate : public Node
471 {
472   public:
473     template <typename CrowApp>
474     HTTPSCertificate(CrowApp &app) :
475         Node(app,
476              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
477              "<str>/",
478              std::string())
479     {
480         entityPrivileges = {
481             {boost::beast::http::verb::get, {{"Login"}}},
482             {boost::beast::http::verb::head, {{"Login"}}},
483             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
484             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
485             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
486             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
487     }
488 
489     void doGet(crow::Response &res, const crow::Request &req,
490                const std::vector<std::string> &params) override
491     {
492         auto asyncResp = std::make_shared<AsyncResp>(res);
493         if (params.size() != 1)
494         {
495             messages::internalError(asyncResp->res);
496             return;
497         }
498         long id = getIDFromURL(req.url);
499 
500         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
501         std::string certURL =
502             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
503             std::to_string(id);
504         std::string objectPath = certs::httpsObjectPath;
505         objectPath += "/";
506         objectPath += std::to_string(id);
507         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
508                                  id, certURL, "HTTPS Certificate");
509     }
510 
511 }; // namespace redfish
512 
513 /**
514  * Collection of HTTPS certificates
515  */
516 class HTTPSCertificateCollection : public Node
517 {
518   public:
519     template <typename CrowApp>
520     HTTPSCertificateCollection(CrowApp &app) :
521         Node(app,
522              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
523     {
524         entityPrivileges = {
525             {boost::beast::http::verb::get, {{"Login"}}},
526             {boost::beast::http::verb::head, {{"Login"}}},
527             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
528             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
529             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
530             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
531     }
532     void doGet(crow::Response &res, const crow::Request &req,
533                const std::vector<std::string> &params) override
534     {
535         res.jsonValue = {
536             {"@odata.id",
537              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
538             {"@odata.type", "#CertificateCollection.CertificateCollection"},
539             {"@odata.context",
540              "/redfish/v1/"
541              "$metadata#CertificateCollection.CertificateCollection"},
542             {"Name", "HTTPS Certificates Collection"},
543             {"Description", "A Collection of HTTPS certificate instances"}};
544         auto asyncResp = std::make_shared<AsyncResp>(res);
545         crow::connections::systemBus->async_method_call(
546             [asyncResp](const boost::system::error_code ec,
547                         const ManagedObjectType &certs) {
548                 if (ec)
549                 {
550                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
551                     messages::internalError(asyncResp->res);
552                     return;
553                 }
554                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
555                 members = nlohmann::json::array();
556                 for (const auto &cert : certs)
557                 {
558                     long id = getIDFromURL(cert.first.str);
559                     if (id >= 0)
560                     {
561                         members.push_back(
562                             {{"@odata.id",
563                               "/redfish/v1/Managers/bmc/"
564                               "NetworkProtocol/HTTPS/Certificates/" +
565                                   std::to_string(id)}});
566                     }
567                 }
568                 asyncResp->res.jsonValue["Members@odata.count"] =
569                     members.size();
570             },
571             certs::httpsServiceName, certs::httpsObjectPath,
572             certs::dbusObjManagerIntf, "GetManagedObjects");
573     }
574 
575     void doPost(crow::Response &res, const crow::Request &req,
576                 const std::vector<std::string> &params) override
577     {
578         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
579         auto asyncResp = std::make_shared<AsyncResp>(res);
580         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
581                                     {"Description", "HTTPS Certificate"}};
582 
583         std::shared_ptr<CertificateFile> certFile =
584             std::make_shared<CertificateFile>(req.body);
585 
586         crow::connections::systemBus->async_method_call(
587             [asyncResp, certFile](const boost::system::error_code ec) {
588                 if (ec)
589                 {
590                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
591                     messages::internalError(asyncResp->res);
592                     return;
593                 }
594                 // TODO: Issue#84 supporting only 1 certificate
595                 long certId = 1;
596                 std::string certURL =
597                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
598                     "Certificates/" +
599                     std::to_string(certId);
600                 std::string objectPath = std::string(certs::httpsObjectPath) +
601                                          "/" + std::to_string(certId);
602                 getCertificateProperties(asyncResp, objectPath,
603                                          certs::httpsServiceName, certId,
604                                          certURL, "HTTPS Certificate");
605                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
606                                  << certFile->getCertFilePath();
607             },
608             certs::httpsServiceName, certs::httpsObjectPath,
609             certs::certInstallIntf, "Install", certFile->getCertFilePath());
610     }
611 }; // HTTPSCertificateCollection
612 
613 /**
614  * The certificate location schema defines a resource that an administrator
615  * can use in order to locate all certificates installed on a given service.
616  */
617 class CertificateLocations : public Node
618 {
619   public:
620     template <typename CrowApp>
621     CertificateLocations(CrowApp &app) :
622         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
623     {
624         entityPrivileges = {
625             {boost::beast::http::verb::get, {{"Login"}}},
626             {boost::beast::http::verb::head, {{"Login"}}},
627             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
628             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
629             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
630             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
631     }
632 
633   private:
634     void doGet(crow::Response &res, const crow::Request &req,
635                const std::vector<std::string> &params) override
636     {
637         res.jsonValue = {
638             {"@odata.id",
639              "/redfish/v1/CertificateService/CertificateLocations"},
640             {"@odata.type",
641              "#CertificateLocations.v1_0_0.CertificateLocations"},
642             {"@odata.context",
643              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
644             {"Name", "Certificate Locations"},
645             {"Id", "CertificateLocations"},
646             {"Description",
647              "Defines a resource that an administrator can use in order to "
648              "locate all certificates installed on a given service"}};
649         auto asyncResp = std::make_shared<AsyncResp>(res);
650         nlohmann::json &links =
651             asyncResp->res.jsonValue["Links"]["Certificates"];
652         links = nlohmann::json::array();
653         getCertificateLocations(
654             asyncResp,
655             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
656             certs::httpsObjectPath, certs::httpsServiceName);
657         getCertificateLocations(asyncResp,
658                                 "/redfish/v1/AccountService/LDAP/Certificates/",
659                                 certs::ldapObjectPath, certs::ldapServiceName);
660     }
661     /**
662      * @brief Retrieve the certificates installed list and append to the
663      * response
664      *
665      * @param[in] asyncResp Shared pointer to the response message
666      * @param[in] certURL  Path of the certificate object
667      * @param[in] path  Path of the D-Bus service object
668      * @return None
669      */
670     void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
671                                  const std::string &certURL,
672                                  const std::string &path,
673                                  const std::string &service)
674     {
675         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
676                          << " Path=" << path << " service= " << service;
677         crow::connections::systemBus->async_method_call(
678             [asyncResp, certURL](const boost::system::error_code ec,
679                                  const ManagedObjectType &certs) {
680                 if (ec)
681                 {
682                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
683                     messages::internalError(asyncResp->res);
684                     return;
685                 }
686                 nlohmann::json &links =
687                     asyncResp->res.jsonValue["Links"]["Certificates"];
688                 for (auto &cert : certs)
689                 {
690                     long id = getIDFromURL(cert.first.str);
691                     if (id >= 0)
692                     {
693                         links.push_back(
694                             {{"@odata.id", certURL + std::to_string(id)}});
695                     }
696                 }
697                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
698                     links.size();
699             },
700             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
701     }
702 }; // CertificateLocations
703 
704 /**
705  * Collection of LDAP certificates
706  */
707 class LDAPCertificateCollection : public Node
708 {
709   public:
710     template <typename CrowApp>
711     LDAPCertificateCollection(CrowApp &app) :
712         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
713     {
714         entityPrivileges = {
715             {boost::beast::http::verb::get, {{"Login"}}},
716             {boost::beast::http::verb::head, {{"Login"}}},
717             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
718             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
719             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
720             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
721     }
722     void doGet(crow::Response &res, const crow::Request &req,
723                const std::vector<std::string> &params) override
724     {
725         res.jsonValue = {
726             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
727             {"@odata.type", "#CertificateCollection.CertificateCollection"},
728             {"@odata.context",
729              "/redfish/v1/"
730              "$metadata#CertificateCollection.CertificateCollection"},
731             {"Name", "LDAP Certificates Collection"},
732             {"Description", "A Collection of LDAP certificate instances"}};
733         auto asyncResp = std::make_shared<AsyncResp>(res);
734         crow::connections::systemBus->async_method_call(
735             [asyncResp](const boost::system::error_code ec,
736                         const ManagedObjectType &certs) {
737                 if (ec)
738                 {
739                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
740                     messages::internalError(asyncResp->res);
741                     return;
742                 }
743                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
744                 members = nlohmann::json::array();
745                 for (const auto &cert : certs)
746                 {
747                     long id = getIDFromURL(cert.first.str);
748                     if (id >= 0)
749                     {
750                         members.push_back(
751                             {{"@odata.id", "/redfish/v1/AccountService/"
752                                            "LDAP/Certificates/" +
753                                                std::to_string(id)}});
754                     }
755                 }
756                 asyncResp->res.jsonValue["Members@odata.count"] =
757                     members.size();
758             },
759             certs::ldapServiceName, certs::ldapObjectPath,
760             certs::dbusObjManagerIntf, "GetManagedObjects");
761     }
762 
763     void doPost(crow::Response &res, const crow::Request &req,
764                 const std::vector<std::string> &params) override
765     {
766         std::shared_ptr<CertificateFile> certFile =
767             std::make_shared<CertificateFile>(req.body);
768         auto asyncResp = std::make_shared<AsyncResp>(res);
769         crow::connections::systemBus->async_method_call(
770             [asyncResp, certFile](const boost::system::error_code ec) {
771                 if (ec)
772                 {
773                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
774                     messages::internalError(asyncResp->res);
775                     return;
776                 }
777                 //// TODO: Issue#84 supporting only 1 certificate
778                 long certId = 1;
779                 std::string certURL =
780                     "/redfish/v1/AccountService/LDAP/Certificates/" +
781                     std::to_string(certId);
782                 std::string objectPath = std::string(certs::ldapObjectPath) +
783                                          "/" + std::to_string(certId);
784                 getCertificateProperties(asyncResp, objectPath,
785                                          certs::ldapServiceName, certId,
786                                          certURL, "LDAP Certificate");
787                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
788                                  << certFile->getCertFilePath();
789             },
790             certs::ldapServiceName, certs::ldapObjectPath,
791             certs::certInstallIntf, "Install", certFile->getCertFilePath());
792     }
793 }; // LDAPCertificateCollection
794 
795 /**
796  * Certificate resource describes a certificate used to prove the identity
797  * of a component, account or service.
798  */
799 class LDAPCertificate : public Node
800 {
801   public:
802     template <typename CrowApp>
803     LDAPCertificate(CrowApp &app) :
804         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
805              std::string())
806     {
807         entityPrivileges = {
808             {boost::beast::http::verb::get, {{"Login"}}},
809             {boost::beast::http::verb::head, {{"Login"}}},
810             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
811             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
812             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
813             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
814     }
815 
816     void doGet(crow::Response &res, const crow::Request &req,
817                const std::vector<std::string> &params) override
818     {
819         auto asyncResp = std::make_shared<AsyncResp>(res);
820         long id = getIDFromURL(req.url);
821         if (id < 0)
822         {
823             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
824             messages::internalError(asyncResp->res);
825             return;
826         }
827         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
828         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
829                               std::to_string(id);
830         std::string objectPath = certs::ldapObjectPath;
831         objectPath += "/";
832         objectPath += std::to_string(id);
833         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
834                                  id, certURL, "LDAP Certificate");
835     }
836 }; // LDAPCertificate
837 } // namespace redfish
838