Redfish: Add PATCH operation support for RemoteRoleMapping

Added PATCH operation support for RemoteRoleMapping property under
LDAP/ActiveDirectory property in AccountService schema.

1. How to add the Role Mapping?

PATCH {"ActiveDirectory":{"RemoteRoleMapping": [{"RemoteGroup":
"Admingroup15","LocalRole": "User"},{"RemoteGroup": "Admingroup13",
"LocalRole": "Administrator"},{"RemoteGroup": "Admingroup14",
"LocalRole": "Operator"}]}}

With the above PATCH request, all the above role mapping gets added.

2. How to delete a specific role mapping?

After adding the above roles mapping, if user want to delete the second mapping
which is ({"RemoteGroup": "Admingroup13", "LocalRole": "Administrator"})

Following PATCH request would be used.
   PATCH {"ActiveDirectory":{"RemoteRoleMapping": [{},null,{}]}}

3. How to update specific role mapping ?

Let's take a case where user want to update the second role mapping
   PATCH {"ActiveDirectory":{"RemoteRoleMapping": [{},{"RemoteGroup":"Admingroup25","LocalRole": "User"},{}]}}
   or
   PATCH {"ActiveDirectory":{"RemoteRoleMapping": [{},{"RemoteGroup":"Admingroup25"},{}]}}  and \
   PATCH {"ActiveDirectory":{"RemoteRoleMapping": [{},{"LocalRole": "User"},{}]}}
Tested:

1. Did a PATCH operation with below given Data:

  ' {"ActiveDirectory":{"RemoteRoleMapping": [{"RemoteGroup": "Admingroup215","LocalRole": "User"}, \
   {"RemoteGroup": "Admingroup213","LocalRole":"Administrator"},{"RemoteGroup":"Admingroup214","LocalRole":"Operator"}]}}'

2. With GET got below given data:

 "RemoteRoleMapping": [
      {
        "LocalRole": "Operator",
        "RemoteGroup": "Admingroup214"
      },
      {
        "LocalRole": "Administrator",
        "RemoteGroup": "Admingroup213"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup215"
      }
    ],

3. Did a PATCH operation with below given Data:

   '{"ActiveDirectory":{"RemoteRoleMapping": [{},null,{}]}}'

4. With GET got below given data:

    "RemoteRoleMapping": [
      {
        "LocalRole": "Operator",
        "RemoteGroup": "Admingroup214"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup215"
      }
    ],

5. Did a PATCH operation with below given Data:

 '{"ActiveDirectory":{"RemoteRoleMapping": [null,null]}}'

6. With GET got below given data:

 "RemoteRoleMapping": []

7. Did a PATCH operation with below given Data:

   '{"ActiveDirectory":{"RemoteRoleMapping": [{"RemoteGroup": "Admingroup215","LocalRole": "User"}, \
   {"RemoteGroup": "Admingroup213","LocalRole":"Administrator"},{"RemoteGroup":"Admingroup214","LocalRole":"Operator"}]}}'

8. With GET got below given data:

 "RemoteRoleMapping": [
      {
        "LocalRole": "Administrator",
        "RemoteGroup": "Admingroup213"
      },
      {
        "LocalRole": "Operator",
        "RemoteGroup": "Admingroup214"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup215"
      }
    ],
9. Did a PATCH operation with below given Data:

'{"ActiveDirectory":{"RemoteRoleMapping": [{"RemoteGroup": "Admingroup25"},{},{}]}}'

10.With GET got below given data:

 "RemoteRoleMapping": [
      {
        "LocalRole": "Administrator",
        "RemoteGroup": "Admingroup25"
      },
      {
        "LocalRole": "Operator",
        "RemoteGroup": "Admingroup214"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup215"
      }
    ],
11. Did a PATCH operation with below given Data:

'{"ActiveDirectory":{"RemoteRoleMapping": [{"LocalRole": "User"},{},{}]}}'

12.With GET got below given data:

 "RemoteRoleMapping": [
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup25"
      },
      {
        "LocalRole": "Operator",
        "RemoteGroup": "Admingroup214"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup215"
      }
    ],

13. Did a PATCH operation with below given Data:

'{"ActiveDirectory":{"RemoteRoleMapping": [{},{"RemoteGroup": "Admingroup26","LocalRole": "User"},{}]}}'

14.With GET got below given data:

 "RemoteRoleMapping": [
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup25"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup26"
      },
      {
        "LocalRole": "User",
        "RemoteGroup": "Admingroup215"
      }
    ],

15. Try to delete the role map when there was no role map entry and get the following error.
  "RemoteRoleMapping/1@Message.ExtendedInfo": [
    {
      "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
      "Message": "The value null for the property RemoteRoleMapping/0 is of a different type than the property can accept.",
      "MessageArgs": [
        "null",
        "RemoteRoleMapping/0"
      ],
      "MessageId": "Base.1.4.0.PropertyValueTypeError",
      "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed.",
      "Severity": "Warning"
    }

Signed-off-by: Ratan Gupta <ratagupt@linux.vnet.ibm.com>
Change-Id: Iaa37221bd6fdc87dbf51755d9425ecd5b07eee6c
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp
index f1a83a9..9dac7e4 100644
--- a/redfish-core/lib/account_service.hpp
+++ b/redfish-core/lib/account_service.hpp
@@ -37,6 +37,8 @@
 constexpr const char* ldapCreateInterface =
     "xyz.openbmc_project.User.Ldap.Create";
 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable";
+constexpr const char* ldapPrivMapperInterface =
+    "xyz.openbmc_project.User.PrivilegeMapper";
 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties";
 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper";
@@ -149,6 +151,178 @@
 }
 
 /**
+ *  @brief validates given JSON input and then calls appropriate method to
+ * create, to delete or to set Rolemapping object based on the given input.
+ *
+ */
+static void handleRoleMapPatch(
+    const std::shared_ptr<AsyncResp>& asyncResp,
+    const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData,
+    const std::string& serverType, std::vector<nlohmann::json>& input)
+{
+    for (size_t index = 0; index < input.size(); index++)
+    {
+        nlohmann::json& thisJson = input[index];
+
+        if (thisJson.is_null())
+        {
+            // delete the existing object
+            if (index < roleMapObjData.size())
+            {
+                crow::connections::systemBus->async_method_call(
+                    [asyncResp, roleMapObjData, serverType,
+                     index](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+                            messages::internalError(asyncResp->res);
+                            return;
+                        }
+                        asyncResp->res
+                            .jsonValue[serverType]["RemoteRoleMapping"][index] =
+                            nullptr;
+                    },
+                    ldapDbusService, roleMapObjData[index].first,
+                    "xyz.openbmc_project.Object.Delete", "Delete");
+            }
+            else
+            {
+                BMCWEB_LOG_ERROR << "Can't delete the object";
+                messages::propertyValueTypeError(
+                    asyncResp->res, thisJson.dump(),
+                    "RemoteRoleMapping/" + std::to_string(index));
+                return;
+            }
+        }
+        else if (thisJson.empty())
+        {
+            // Don't do anything for the empty objects,parse next json
+            // eg {"RemoteRoleMapping",[{}]}
+        }
+        else
+        {
+            // update/create the object
+            std::optional<std::string> remoteGroup;
+            std::optional<std::string> localRole;
+
+            if (!json_util::readJson(thisJson, asyncResp->res, "RemoteGroup",
+                                     remoteGroup, "LocalRole", localRole))
+            {
+                continue;
+            }
+
+            // Update existing RoleMapping Object
+            if (index < roleMapObjData.size())
+            {
+                BMCWEB_LOG_DEBUG << "Update Role Map Object";
+                // If "RemoteGroup" info is provided
+                if (remoteGroup)
+                {
+                    crow::connections::systemBus->async_method_call(
+                        [asyncResp, roleMapObjData, serverType, index,
+                         remoteGroup](const boost::system::error_code ec) {
+                            if (ec)
+                            {
+                                BMCWEB_LOG_ERROR << "DBUS response error: "
+                                                 << ec;
+                                messages::internalError(asyncResp->res);
+                                return;
+                            }
+                            asyncResp->res
+                                .jsonValue[serverType]["RemoteRoleMapping"]
+                                          [index]["RemoteGroup"] = *remoteGroup;
+                        },
+                        ldapDbusService, roleMapObjData[index].first,
+                        propertyInterface, "Set",
+                        "xyz.openbmc_project.User.PrivilegeMapperEntry",
+                        "GroupName",
+                        std::variant<std::string>(std::move(*remoteGroup)));
+                }
+
+                // If "LocalRole" info is provided
+                if (localRole)
+                {
+                    crow::connections::systemBus->async_method_call(
+                        [asyncResp, roleMapObjData, serverType, index,
+                         localRole](const boost::system::error_code ec) {
+                            if (ec)
+                            {
+                                BMCWEB_LOG_ERROR << "DBUS response error: "
+                                                 << ec;
+                                messages::internalError(asyncResp->res);
+                                return;
+                            }
+                            asyncResp->res
+                                .jsonValue[serverType]["RemoteRoleMapping"]
+                                          [index]["LocalRole"] = *localRole;
+                        },
+                        ldapDbusService, roleMapObjData[index].first,
+                        propertyInterface, "Set",
+                        "xyz.openbmc_project.User.PrivilegeMapperEntry",
+                        "Privilege",
+                        std::variant<std::string>(
+                            getPrivilegeFromRoleId(std::move(*localRole))));
+                }
+            }
+            // Create a new RoleMapping Object.
+            else
+            {
+                BMCWEB_LOG_DEBUG
+                    << "setRoleMappingProperties: Creating new Object";
+                std::string pathString =
+                    "RemoteRoleMapping/" + std::to_string(index);
+
+                if (!localRole)
+                {
+                    messages::propertyMissing(asyncResp->res,
+                                              pathString + "/LocalRole");
+                    continue;
+                }
+                if (!remoteGroup)
+                {
+                    messages::propertyMissing(asyncResp->res,
+                                              pathString + "/RemoteGroup");
+                    continue;
+                }
+
+                std::string dbusObjectPath;
+                if (serverType == "ActiveDirectory")
+                {
+                    dbusObjectPath = ADConfigObject;
+                }
+                else if (serverType == "LDAP")
+                {
+                    dbusObjectPath = ldapConfigObject;
+                }
+
+                BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup
+                                 << ",LocalRole=" << *localRole;
+
+                crow::connections::systemBus->async_method_call(
+                    [asyncResp, serverType, index, localRole,
+                     remoteGroup](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+                            messages::internalError(asyncResp->res);
+                            return;
+                        }
+                        nlohmann::json& remoteRoleJson =
+                            asyncResp->res
+                                .jsonValue[serverType]["RemoteRoleMapping"];
+                        remoteRoleJson.push_back(
+                            {{"LocalRole", *localRole},
+                             {"RemoteGroup", *remoteGroup}});
+                    },
+                    ldapDbusService, dbusObjectPath, ldapPrivMapperInterface,
+                    "Create", std::move(*remoteGroup),
+                    getPrivilegeFromRoleId(std::move(*localRole)));
+            }
+        }
+    }
+}
+
+/**
  * Function that retrieves all properties for LDAP config object
  * into JSON
  */
@@ -699,12 +873,14 @@
         std::optional<std::string> groupsAttribute;
         std::optional<std::string> userName;
         std::optional<std::string> password;
+        std::optional<std::vector<nlohmann::json>> remoteRoleMapData;
 
         if (!json_util::readJson(input, asyncResp->res, "Authentication",
                                  authentication, "LDAPService", ldapService,
                                  "ServiceAddresses", serviceAddressList,
                                  "AccountProviderType", accountProviderType,
-                                 "ServiceEnabled", serviceEnabled))
+                                 "ServiceEnabled", serviceEnabled,
+                                 "RemoteRoleMapping", remoteRoleMapData))
         {
             return;
         }
@@ -745,7 +921,8 @@
 
         // nothing to update, then return
         if (!userName && !password && !serviceAddressList && !baseDNList &&
-            !userNameAttribute && !groupsAttribute && !serviceEnabled)
+            !userNameAttribute && !groupsAttribute && !serviceEnabled &&
+            !remoteRoleMapData)
         {
             return;
         }
@@ -756,7 +933,7 @@
                                        baseDNList, userNameAttribute,
                                        groupsAttribute, accountProviderType,
                                        serviceAddressList, serviceEnabled,
-                                       dbusObjectPath](
+                                       dbusObjectPath, remoteRoleMapData](
                                           bool success, LDAPConfigData confData,
                                           const std::string& serverType) {
             if (!success)
@@ -823,6 +1000,15 @@
                 handleServiceEnablePatch(confData.serviceEnabled, asyncResp,
                                          serverType, dbusObjectPath);
             }
+
+            if (remoteRoleMapData)
+            {
+                std::vector<nlohmann::json> remoteRoleMap =
+                    std::move(*remoteRoleMapData);
+
+                handleRoleMapPatch(asyncResp, confData.groupRoleList,
+                                   serverType, remoteRoleMap);
+            }
         });
     }