Implement AccountService PATCH method

This patchset implements the AccountService PATCH method, using PAM and
dbus in combination.

Change-Id: I754590f787fc84a21a9453e7e10726c56da5c3f7
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
index f51f9aa..643c804 100644
--- a/include/pam_authenticate.hpp
+++ b/include/pam_authenticate.hpp
@@ -55,14 +55,6 @@
 
     if (retval != PAM_SUCCESS)
     {
-        if (retval == PAM_AUTH_ERR)
-        {
-            // printf("Authentication failure.\n");
-        }
-        else
-        {
-            // printf("pam_authenticate returned %d\n", retval);
-        }
         pam_end(localAuthHandle, PAM_SUCCESS);
         return false;
     }
@@ -82,3 +74,31 @@
 
     return true;
 }
+
+inline bool pamUpdatePassword(const std::string& username,
+                              const std::string& password)
+{
+    const struct pam_conv localConversation = {
+        pamFunctionConversation, const_cast<char*>(password.c_str())};
+    pam_handle_t* localAuthHandle = NULL; // this gets set by pam_start
+
+    if (pam_start("passwd", username.c_str(), &localConversation,
+                  &localAuthHandle) != PAM_SUCCESS)
+    {
+        return false;
+    }
+    int retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
+
+    if (retval != PAM_SUCCESS)
+    {
+        pam_end(localAuthHandle, PAM_SUCCESS);
+        return false;
+    }
+
+    if (pam_end(localAuthHandle, PAM_SUCCESS) != PAM_SUCCESS)
+    {
+        return false;
+    }
+
+    return true;
+}
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp
index 23e77a7..1f88bb3 100644
--- a/redfish-core/lib/account_service.hpp
+++ b/redfish-core/lib/account_service.hpp
@@ -17,6 +17,7 @@
 #include "node.hpp"
 
 #include <openbmc_dbus_rest.hpp>
+#include <utils/json_utils.hpp>
 
 namespace redfish
 {
@@ -134,6 +135,23 @@
     }
 };
 
+template <typename Callback>
+inline void checkDbusPathExists(const std::string& path, Callback&& callback)
+{
+    using GetObjectType =
+        std::vector<std::pair<std::string, std::vector<std::string>>>;
+
+    crow::connections::systemBus->async_method_call(
+        [callback{std::move(callback)}](const boost::system::error_code ec,
+                                        const GetObjectType& object_names) {
+            callback(ec || object_names.size() == 0);
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetObject", path,
+        std::array<std::string, 0>());
+}
+
 class ManagerAccount : public Node
 {
   public:
@@ -218,6 +236,116 @@
             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
     }
+
+    void doPatch(crow::Response& res, const crow::Request& req,
+                 const std::vector<std::string>& params) override
+    {
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        if (params.size() != 1)
+        {
+            res.result(boost::beast::http::status::internal_server_error);
+            return;
+        }
+
+        nlohmann::json patchRequest;
+        if (!json_util::processJsonFromRequest(res, req, patchRequest))
+        {
+            return;
+        }
+
+        // Check the user exists before updating the fields
+        checkDbusPathExists(
+            "/xyz/openbmc_project/users/" + params[0],
+            [username{std::string(params[0])},
+             patchRequest(std::move(patchRequest)),
+             asyncResp](bool userExists) {
+                if (!userExists)
+                {
+                    messages::addMessageToErrorJson(
+                        asyncResp->res.jsonValue,
+                        messages::resourceNotFound(
+                            "#ManagerAccount.v1_0_3.ManagerAccount", username));
+
+                    asyncResp->res.result(
+                        boost::beast::http::status::not_found);
+                    return;
+                }
+
+                for (const auto& item : patchRequest.items())
+                {
+                    if (item.key() == "Password")
+                    {
+                        const std::string* passStr =
+                            item.value().get_ptr<const std::string*>();
+                        if (passStr == nullptr)
+                        {
+                            messages::addMessageToErrorJson(
+                                asyncResp->res.jsonValue,
+                                messages::propertyValueFormatError(
+                                    item.value().dump(), "Password"));
+                            return;
+                        }
+                        BMCWEB_LOG_DEBUG << "Updating user: " << username
+                                         << " to password " << *passStr;
+                        if (!pamUpdatePassword(username, *passStr))
+                        {
+                            BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
+                            asyncResp->res.result(boost::beast::http::status::
+                                                      internal_server_error);
+                            return;
+                        }
+                    }
+                    else if (item.key() == "Enabled")
+                    {
+                        const bool* enabledBool =
+                            item.value().get_ptr<const bool*>();
+
+                        if (enabledBool == nullptr)
+                        {
+                            messages::addMessageToErrorJson(
+                                asyncResp->res.jsonValue,
+                                messages::propertyValueFormatError(
+                                    item.value().dump(), "Enabled"));
+                            return;
+                        }
+                        crow::connections::systemBus->async_method_call(
+                            [asyncResp](const boost::system::error_code ec) {
+                                if (ec)
+                                {
+                                    BMCWEB_LOG_ERROR
+                                        << "D-Bus responses error: " << ec;
+                                    asyncResp->res.result(
+                                        boost::beast::http::status::
+                                            internal_server_error);
+                                    return;
+                                }
+                                // TODO Consider support polling mechanism to
+                                // verify status of host and chassis after
+                                // execute the requested action.
+                                BMCWEB_LOG_DEBUG << "Response with no content";
+                                asyncResp->res.result(
+                                    boost::beast::http::status::no_content);
+                            },
+                            "xyz.openbmc_project.User.Manager",
+                            "/xyz/openbmc_project/users/" + username,
+                            "org.freedesktop.DBus.Properties", "Set",
+                            "xyz.openbmc_project.User.Attributes"
+                            "UserEnabled",
+                            sdbusplus::message::variant<bool>{*enabledBool});
+                    }
+                    else
+                    {
+                        messages::addMessageToErrorJson(
+                            asyncResp->res.jsonValue,
+                            messages::propertyNotWritable(item.key()));
+                        asyncResp->res.result(
+                            boost::beast::http::status::bad_request);
+                        return;
+                    }
+                }
+            });
+    }
 };
 
 } // namespace redfish