xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 37cce918ede6489ab980712c243b526a30396ca4)
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::internalError(asyncResp->res);
261                 return;
262             }
263             asyncResp->res.jsonValue = {
264                 {"@odata.id", certURL},
265                 {"@odata.type", "#Certificate.v1_0_0.Certificate"},
266                 {"@odata.context",
267                  "/redfish/v1/$metadata#Certificate.Certificate"},
268                 {"Id", std::to_string(certId)},
269                 {"Name", name},
270                 {"Description", name}};
271             for (const auto &property : properties)
272             {
273                 if (property.first == "CertificateString")
274                 {
275                     asyncResp->res.jsonValue["CertificateString"] = "";
276                     const std::string *value =
277                         std::get_if<std::string>(&property.second);
278                     if (value)
279                     {
280                         asyncResp->res.jsonValue["CertificateString"] = *value;
281                     }
282                 }
283                 else if (property.first == "KeyUsage")
284                 {
285                     nlohmann::json &keyUsage =
286                         asyncResp->res.jsonValue["KeyUsage"];
287                     keyUsage = nlohmann::json::array();
288                     const std::vector<std::string> *value =
289                         std::get_if<std::vector<std::string>>(&property.second);
290                     if (value)
291                     {
292                         for (const std::string &usage : *value)
293                         {
294                             keyUsage.push_back(usage);
295                         }
296                     }
297                 }
298                 else if (property.first == "Issuer")
299                 {
300                     const std::string *value =
301                         std::get_if<std::string>(&property.second);
302                     if (value)
303                     {
304                         updateCertIssuerOrSubject(
305                             asyncResp->res.jsonValue["Issuer"], *value);
306                     }
307                 }
308                 else if (property.first == "Subject")
309                 {
310                     const std::string *value =
311                         std::get_if<std::string>(&property.second);
312                     if (value)
313                     {
314                         updateCertIssuerOrSubject(
315                             asyncResp->res.jsonValue["Subject"], *value);
316                     }
317                 }
318                 else if (property.first == "ValidNotAfter")
319                 {
320                     const uint64_t *value =
321                         std::get_if<uint64_t>(&property.second);
322                     if (value)
323                     {
324                         std::time_t time = static_cast<std::time_t>(*value);
325                         asyncResp->res.jsonValue["ValidNotAfter"] =
326                             crow::utility::getDateTime(time);
327                     }
328                 }
329                 else if (property.first == "ValidNotBefore")
330                 {
331                     const uint64_t *value =
332                         std::get_if<uint64_t>(&property.second);
333                     if (value)
334                     {
335                         std::time_t time = static_cast<std::time_t>(*value);
336                         asyncResp->res.jsonValue["ValidNotBefore"] =
337                             crow::utility::getDateTime(time);
338                     }
339                 }
340             }
341             asyncResp->res.addHeader("Location", certURL);
342         },
343         service, objectPath, certs::dbusPropIntf, "GetAll",
344         certs::certPropIntf);
345 }
346 
347 using GetObjectType =
348     std::vector<std::pair<std::string, std::vector<std::string>>>;
349 
350 /**
351  * Action to replace an existing certificate
352  */
353 class CertificateActionsReplaceCertificate : public Node
354 {
355   public:
356     CertificateActionsReplaceCertificate(CrowApp &app) :
357         Node(app, "/redfish/v1/CertificateService/Actions/"
358                   "CertificateService.ReplaceCertificate/")
359     {
360         entityPrivileges = {
361             {boost::beast::http::verb::get, {{"Login"}}},
362             {boost::beast::http::verb::head, {{"Login"}}},
363             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
364             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
365             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
366             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
367     }
368 
369   private:
370     void doPost(crow::Response &res, const crow::Request &req,
371                 const std::vector<std::string> &params) override
372     {
373         std::string certificate;
374         nlohmann::json certificateUri;
375         std::optional<std::string> certificateType = "PEM";
376         auto asyncResp = std::make_shared<AsyncResp>(res);
377         if (!json_util::readJson(req, asyncResp->res, "CertificateString",
378                                  certificate, "CertificateUri", certificateUri,
379                                  "CertificateType", certificateType))
380         {
381             BMCWEB_LOG_ERROR << "Required parameters are missing";
382             messages::internalError(asyncResp->res);
383             return;
384         }
385 
386         if (!certificateType)
387         {
388             // should never happen, but it never hurts to be paranoid.
389             return;
390         }
391         if (certificateType != "PEM")
392         {
393             messages::actionParameterNotSupported(
394                 asyncResp->res, "CertificateType", "ReplaceCertificate");
395             return;
396         }
397 
398         std::string certURI;
399         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
400                                           "@odata.id", certURI))
401         {
402             messages::actionParameterMissing(
403                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
404             return;
405         }
406 
407         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
408         long id = getIDFromURL(certURI);
409         if (id < 0)
410         {
411             messages::actionParameterValueFormatError(asyncResp->res, certURI,
412                                                       "CertificateUri",
413                                                       "ReplaceCertificate");
414             return;
415         }
416         std::string objectPath;
417         std::string name;
418         std::string service;
419         if (boost::starts_with(
420                 certURI,
421                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
422         {
423             objectPath =
424                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
425             name = "HTTPS certificate";
426             service = certs::httpsServiceName;
427         }
428         else if (boost::starts_with(
429                      certURI, "/redfish/v1/AccountService/LDAP/Certificates/"))
430         {
431             objectPath =
432                 std::string(certs::ldapObjectPath) + "/" + std::to_string(id);
433             name = "LDAP certificate";
434             service = certs::ldapServiceName;
435         }
436         else
437         {
438             messages::actionParameterNotSupported(
439                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
440             return;
441         }
442 
443         std::shared_ptr<CertificateFile> certFile =
444             std::make_shared<CertificateFile>(certificate);
445         crow::connections::systemBus->async_method_call(
446             [asyncResp, certFile, objectPath, service, certURI, id,
447              name](const boost::system::error_code ec) {
448                 if (ec)
449                 {
450                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
451                     messages::internalError(asyncResp->res);
452                     return;
453                 }
454                 getCertificateProperties(asyncResp, objectPath, service, id,
455                                          certURI, name);
456                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
457                                  << certFile->getCertFilePath();
458             },
459             service, objectPath, certs::certReplaceIntf, "Replace",
460             certFile->getCertFilePath());
461     }
462 }; // CertificateActionsReplaceCertificate
463 
464 /**
465  * Certificate resource describes a certificate used to prove the identity
466  * of a component, account or service.
467  */
468 class HTTPSCertificate : public Node
469 {
470   public:
471     template <typename CrowApp>
472     HTTPSCertificate(CrowApp &app) :
473         Node(app,
474              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
475              "<str>/",
476              std::string())
477     {
478         entityPrivileges = {
479             {boost::beast::http::verb::get, {{"Login"}}},
480             {boost::beast::http::verb::head, {{"Login"}}},
481             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
482             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
483             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
484             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
485     }
486 
487     void doGet(crow::Response &res, const crow::Request &req,
488                const std::vector<std::string> &params) override
489     {
490         auto asyncResp = std::make_shared<AsyncResp>(res);
491         if (params.size() != 1)
492         {
493             messages::internalError(asyncResp->res);
494             return;
495         }
496         long id = getIDFromURL(req.url);
497 
498         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
499         std::string certURL =
500             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
501             std::to_string(id);
502         std::string objectPath = certs::httpsObjectPath;
503         objectPath += "/";
504         objectPath += std::to_string(id);
505         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
506                                  id, certURL, "HTTPS Certificate");
507     }
508 
509 }; // namespace redfish
510 
511 /**
512  * Collection of HTTPS certificates
513  */
514 class HTTPSCertificateCollection : public Node
515 {
516   public:
517     template <typename CrowApp>
518     HTTPSCertificateCollection(CrowApp &app) :
519         Node(app,
520              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
521     {
522         entityPrivileges = {
523             {boost::beast::http::verb::get, {{"Login"}}},
524             {boost::beast::http::verb::head, {{"Login"}}},
525             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
526             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
527             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
528             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
529     }
530     void doGet(crow::Response &res, const crow::Request &req,
531                const std::vector<std::string> &params) override
532     {
533         res.jsonValue = {
534             {"@odata.id",
535              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
536             {"@odata.type", "#CertificateCollection.CertificateCollection"},
537             {"@odata.context",
538              "/redfish/v1/"
539              "$metadata#CertificateCollection.CertificateCollection"},
540             {"Name", "HTTPS Certificates Collection"},
541             {"Description", "A Collection of HTTPS certificate instances"}};
542         auto asyncResp = std::make_shared<AsyncResp>(res);
543         crow::connections::systemBus->async_method_call(
544             [asyncResp](const boost::system::error_code ec,
545                         const ManagedObjectType &certs) {
546                 if (ec)
547                 {
548                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
549                     messages::internalError(asyncResp->res);
550                     return;
551                 }
552                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
553                 members = nlohmann::json::array();
554                 for (const auto &cert : certs)
555                 {
556                     long id = getIDFromURL(cert.first.str);
557                     if (id >= 0)
558                     {
559                         members.push_back(
560                             {{"@odata.id",
561                               "/redfish/v1/Managers/bmc/"
562                               "NetworkProtocol/HTTPS/Certificates/" +
563                                   std::to_string(id)}});
564                     }
565                 }
566                 asyncResp->res.jsonValue["Members@odata.count"] =
567                     members.size();
568             },
569             certs::httpsServiceName, certs::httpsObjectPath,
570             certs::dbusObjManagerIntf, "GetManagedObjects");
571     }
572 
573     void doPost(crow::Response &res, const crow::Request &req,
574                 const std::vector<std::string> &params) override
575     {
576         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
577         auto asyncResp = std::make_shared<AsyncResp>(res);
578         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
579                                     {"Description", "HTTPS Certificate"}};
580 
581         std::shared_ptr<CertificateFile> certFile =
582             std::make_shared<CertificateFile>(req.body);
583 
584         crow::connections::systemBus->async_method_call(
585             [asyncResp, certFile](const boost::system::error_code ec) {
586                 if (ec)
587                 {
588                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
589                     messages::internalError(asyncResp->res);
590                     return;
591                 }
592                 // TODO: Issue#84 supporting only 1 certificate
593                 long certId = 1;
594                 std::string certURL =
595                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
596                     "Certificates/" +
597                     std::to_string(certId);
598                 std::string objectPath = std::string(certs::httpsObjectPath) +
599                                          "/" + std::to_string(certId);
600                 getCertificateProperties(asyncResp, objectPath,
601                                          certs::httpsServiceName, certId,
602                                          certURL, "HTTPS Certificate");
603                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
604                                  << certFile->getCertFilePath();
605             },
606             certs::httpsServiceName, certs::httpsObjectPath,
607             certs::certInstallIntf, "Install", certFile->getCertFilePath());
608     }
609 }; // HTTPSCertificateCollection
610 
611 /**
612  * The certificate location schema defines a resource that an administrator
613  * can use in order to locate all certificates installed on a given service.
614  */
615 class CertificateLocations : public Node
616 {
617   public:
618     template <typename CrowApp>
619     CertificateLocations(CrowApp &app) :
620         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
621     {
622         entityPrivileges = {
623             {boost::beast::http::verb::get, {{"Login"}}},
624             {boost::beast::http::verb::head, {{"Login"}}},
625             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
626             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
627             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
628             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
629     }
630 
631   private:
632     void doGet(crow::Response &res, const crow::Request &req,
633                const std::vector<std::string> &params) override
634     {
635         res.jsonValue = {
636             {"@odata.id",
637              "/redfish/v1/CertificateService/CertificateLocations"},
638             {"@odata.type",
639              "#CertificateLocations.v1_0_0.CertificateLocations"},
640             {"@odata.context",
641              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
642             {"Name", "Certificate Locations"},
643             {"Id", "CertificateLocations"},
644             {"Description",
645              "Defines a resource that an administrator can use in order to "
646              "locate all certificates installed on a given service"}};
647         auto asyncResp = std::make_shared<AsyncResp>(res);
648         nlohmann::json &links =
649             asyncResp->res.jsonValue["Links"]["Certificates"];
650         links = nlohmann::json::array();
651         getCertificateLocations(
652             asyncResp,
653             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
654             certs::httpsObjectPath, certs::httpsServiceName);
655         getCertificateLocations(asyncResp,
656                                 "/redfish/v1/AccountService/LDAP/Certificates/",
657                                 certs::ldapObjectPath, certs::ldapServiceName);
658     }
659     /**
660      * @brief Retrieve the certificates installed list and append to the
661      * response
662      *
663      * @param[in] asyncResp Shared pointer to the response message
664      * @param[in] certURL  Path of the certificate object
665      * @param[in] path  Path of the D-Bus service object
666      * @return None
667      */
668     void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
669                                  const std::string &certURL,
670                                  const std::string &path,
671                                  const std::string &service)
672     {
673         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
674                          << " Path=" << path << " service= " << service;
675         crow::connections::systemBus->async_method_call(
676             [asyncResp, certURL](const boost::system::error_code ec,
677                                  const ManagedObjectType &certs) {
678                 if (ec)
679                 {
680                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
681                     messages::internalError(asyncResp->res);
682                     return;
683                 }
684                 nlohmann::json &links =
685                     asyncResp->res.jsonValue["Links"]["Certificates"];
686                 for (auto &cert : certs)
687                 {
688                     long id = getIDFromURL(cert.first.str);
689                     if (id >= 0)
690                     {
691                         links.push_back(
692                             {{"@odata.id", certURL + std::to_string(id)}});
693                     }
694                 }
695                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
696                     links.size();
697             },
698             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
699     }
700 }; // CertificateLocations
701 
702 /**
703  * Collection of LDAP certificates
704  */
705 class LDAPCertificateCollection : public Node
706 {
707   public:
708     template <typename CrowApp>
709     LDAPCertificateCollection(CrowApp &app) :
710         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
711     {
712         entityPrivileges = {
713             {boost::beast::http::verb::get, {{"Login"}}},
714             {boost::beast::http::verb::head, {{"Login"}}},
715             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
716             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
717             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
718             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
719     }
720     void doGet(crow::Response &res, const crow::Request &req,
721                const std::vector<std::string> &params) override
722     {
723         res.jsonValue = {
724             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
725             {"@odata.type", "#CertificateCollection.CertificateCollection"},
726             {"@odata.context",
727              "/redfish/v1/"
728              "$metadata#CertificateCollection.CertificateCollection"},
729             {"Name", "LDAP Certificates Collection"},
730             {"Description", "A Collection of LDAP certificate instances"}};
731         auto asyncResp = std::make_shared<AsyncResp>(res);
732         crow::connections::systemBus->async_method_call(
733             [asyncResp](const boost::system::error_code ec,
734                         const ManagedObjectType &certs) {
735                 if (ec)
736                 {
737                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
738                     messages::internalError(asyncResp->res);
739                     return;
740                 }
741                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
742                 members = nlohmann::json::array();
743                 for (const auto &cert : certs)
744                 {
745                     long id = getIDFromURL(cert.first.str);
746                     if (id >= 0)
747                     {
748                         members.push_back(
749                             {{"@odata.id", "/redfish/v1/AccountService/"
750                                            "LDAP/Certificates/" +
751                                                std::to_string(id)}});
752                     }
753                 }
754                 asyncResp->res.jsonValue["Members@odata.count"] =
755                     members.size();
756             },
757             certs::ldapServiceName, certs::ldapObjectPath,
758             certs::dbusObjManagerIntf, "GetManagedObjects");
759     }
760 
761     void doPost(crow::Response &res, const crow::Request &req,
762                 const std::vector<std::string> &params) override
763     {
764         std::shared_ptr<CertificateFile> certFile =
765             std::make_shared<CertificateFile>(req.body);
766         auto asyncResp = std::make_shared<AsyncResp>(res);
767         crow::connections::systemBus->async_method_call(
768             [asyncResp, certFile](const boost::system::error_code ec) {
769                 if (ec)
770                 {
771                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
772                     messages::internalError(asyncResp->res);
773                     return;
774                 }
775                 //// TODO: Issue#84 supporting only 1 certificate
776                 long certId = 1;
777                 std::string certURL =
778                     "/redfish/v1/AccountService/LDAP/Certificates/" +
779                     std::to_string(certId);
780                 std::string objectPath = std::string(certs::ldapObjectPath) +
781                                          "/" + std::to_string(certId);
782                 getCertificateProperties(asyncResp, objectPath,
783                                          certs::ldapServiceName, certId,
784                                          certURL, "LDAP Certificate");
785                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
786                                  << certFile->getCertFilePath();
787             },
788             certs::ldapServiceName, certs::ldapObjectPath,
789             certs::certInstallIntf, "Install", certFile->getCertFilePath());
790     }
791 }; // LDAPCertificateCollection
792 
793 /**
794  * Certificate resource describes a certificate used to prove the identity
795  * of a component, account or service.
796  */
797 class LDAPCertificate : public Node
798 {
799   public:
800     template <typename CrowApp>
801     LDAPCertificate(CrowApp &app) :
802         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
803              std::string())
804     {
805         entityPrivileges = {
806             {boost::beast::http::verb::get, {{"Login"}}},
807             {boost::beast::http::verb::head, {{"Login"}}},
808             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
809             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
810             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
811             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
812     }
813 
814     void doGet(crow::Response &res, const crow::Request &req,
815                const std::vector<std::string> &params) override
816     {
817         auto asyncResp = std::make_shared<AsyncResp>(res);
818         long id = getIDFromURL(req.url);
819         if (id < 0)
820         {
821             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
822             messages::internalError(asyncResp->res);
823             return;
824         }
825         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
826         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
827                               std::to_string(id);
828         std::string objectPath = certs::ldapObjectPath;
829         objectPath += "/";
830         objectPath += std::to_string(id);
831         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
832                                  id, certURL, "LDAP Certificate");
833     }
834 }; // LDAPCertificate
835 } // namespace redfish
836