Implement createGroup/deleteGroup

This commit adds the implementation for CreateGroup and DeleteGroup.

These interfaces give the possibility to create OEMRole and OEMPrivilege
as proposed in the Dynamic Redfish Authz design.
[1] https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md

Since now secondary groups will change at runtime, this commit made the
|groupsMgr| non-constant. When the service starts up, it will load all
the groups in the system, and recover its |groupsMgr| in memory.
Currently, only groups with certain prefixes are allowed to change
(creation or deletion). The only use case now is Redfish previleges and
roles so the current prefixes only cover that.

Similar to user creation, this commit also added limits and checks to
make sure these interfaces are safe.

Coverage:
  lines......: 84.1% (2197 of 2613 lines)
  functions..: 94.3% (492 of 522 functions)
  branches...: 31.1% (3506 of 11263 branches)

Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Change-Id: I245017afda909a0bfa594ef112d7b0d40045f80d
diff --git a/test/user_mgr_test.cpp b/test/user_mgr_test.cpp
index 5db642e..8de2893 100644
--- a/test/user_mgr_test.cpp
+++ b/test/user_mgr_test.cpp
@@ -8,7 +8,9 @@
 #include <exception>
 #include <filesystem>
 #include <fstream>
+#include <vector>
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 namespace phosphor
@@ -278,6 +280,16 @@
 
         ON_CALL(*this, executeUserModifyUserEnable)
             .WillByDefault(testing::Return());
+
+        ON_CALL(*this, executeGroupCreation(testing::_))
+            .WillByDefault(testing::Return());
+
+        ON_CALL(*this, executeGroupDeletion(testing::_))
+            .WillByDefault(testing::Return());
+
+        ON_CALL(*this, executeGroupCreation).WillByDefault(testing::Return());
+
+        ON_CALL(*this, executeGroupDeletion).WillByDefault(testing::Return());
     }
 
     ~UserMgrInTest() override
@@ -304,6 +316,10 @@
     MOCK_METHOD(std::vector<std::string>, getFailedAttempt, (const char*),
                 (override));
 
+    MOCK_METHOD(void, executeGroupCreation, (const char*), (override));
+
+    MOCK_METHOD(void, executeGroupDeletion, (const char*), (override));
+
   protected:
     static sdbusplus::bus_t busInTest;
     std::string tempPamConfigFile;
@@ -824,5 +840,160 @@
     EXPECT_EQ(userLockedForFailedAttempt(username), false);
 }
 
+TEST_F(UserMgrInTest, CheckAndThrowForDisallowedGroupCreationOnSuccess)
+{
+    // Base Redfish Roles
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfr_Administrator"));
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfr_Operator"));
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfr_ReadOnly"));
+    // Base Redfish Privileges
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfp_Login"));
+    EXPECT_NO_THROW(checkAndThrowForDisallowedGroupCreation(
+        "openbmc_rfp_ConfigureManager"));
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfp_ConfigureUsers"));
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfp_ConfigureSelf"));
+    EXPECT_NO_THROW(checkAndThrowForDisallowedGroupCreation(
+        "openbmc_rfp_ConfigureComponents"));
+    // OEM Redfish Roles
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_orfr_PowerService"));
+    // OEM Redfish Privileges
+    EXPECT_NO_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_orfp_PowerService"));
+}
+
+TEST_F(UserMgrInTest,
+       CheckAndThrowForDisallowedGroupCreationThrowsIfGroupNameTooLong)
+{
+    std::string groupName(maxSystemGroupNameLength + 1, 'A');
+    EXPECT_THROW(
+        checkAndThrowForDisallowedGroupCreation(groupName),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+}
+
+TEST_F(
+    UserMgrInTest,
+    CheckAndThrowForDisallowedGroupCreationThrowsIfGroupNameHasDisallowedCharacters)
+{
+
+    EXPECT_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfp_?owerService"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+    EXPECT_THROW(
+        checkAndThrowForDisallowedGroupCreation("openbmc_rfp_-owerService"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+}
+
+TEST_F(
+    UserMgrInTest,
+    CheckAndThrowForDisallowedGroupCreationThrowsIfGroupNameHasDisallowedPrefix)
+{
+
+    EXPECT_THROW(
+        checkAndThrowForDisallowedGroupCreation("google_rfp_"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+    EXPECT_THROW(
+        checkAndThrowForDisallowedGroupCreation("com_rfp_"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+}
+
+TEST_F(UserMgrInTest, CheckAndThrowForMaxGroupCountOnSuccess)
+{
+    EXPECT_THAT(allGroups().size(), 4);
+    for (size_t i = 0; i < maxSystemGroupCount - 4; ++i)
+    {
+        std::string groupName = "openbmc_rfr_role";
+        groupName += std::to_string(i);
+        EXPECT_NO_THROW(createGroup(groupName));
+    }
+    EXPECT_THROW(
+        createGroup("openbmc_rfr_AnotherRole"),
+        sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource);
+    for (size_t i = 0; i < maxSystemGroupCount - 4; ++i)
+    {
+        std::string groupName = "openbmc_rfr_role";
+        groupName += std::to_string(i);
+        EXPECT_NO_THROW(deleteGroup(groupName));
+    }
+}
+
+TEST_F(UserMgrInTest, CheckAndThrowForGroupExist)
+{
+    std::string groupName = "openbmc_rfr_role";
+    EXPECT_NO_THROW(createGroup(groupName));
+    EXPECT_THROW(
+        createGroup(groupName),
+        sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists);
+    EXPECT_NO_THROW(deleteGroup(groupName));
+}
+
+TEST_F(UserMgrInTest, ByDefaultAllGroupsArePredefinedGroups)
+{
+    EXPECT_THAT(allGroups(),
+                testing::UnorderedElementsAre("web", "redfish", "ipmi", "ssh"));
+}
+
+TEST_F(UserMgrInTest, DeleteGroupThrowsIfGroupIsNotAllowedToChange)
+{
+    EXPECT_THROW(
+        deleteGroup("ipmi"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+    EXPECT_THROW(
+        deleteGroup("web"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+    EXPECT_THROW(
+        deleteGroup("redfish"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+    EXPECT_THROW(
+        deleteGroup("ssh"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+}
+
+TEST_F(UserMgrInTest,
+       CreateGroupThrowsInternalFailureWhenExecuteGroupCreateFails)
+{
+    EXPECT_CALL(*this, executeGroupCreation)
+        .WillOnce(testing::Throw(
+            sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
+    EXPECT_THROW(
+        createGroup("openbmc_rfr_role1"),
+        sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
+}
+
+TEST_F(UserMgrInTest,
+       DeleteGroupThrowsInternalFailureWhenExecuteGroupDeleteFails)
+{
+    std::string groupName = "openbmc_rfr_role1";
+    EXPECT_NO_THROW(UserMgr::createGroup(groupName));
+    EXPECT_CALL(*this, executeGroupDeletion(testing::StrEq(groupName)))
+        .WillOnce(testing::Throw(
+            sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()))
+        .WillOnce(testing::DoDefault());
+
+    EXPECT_THROW(
+        deleteGroup(groupName),
+        sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
+    EXPECT_NO_THROW(UserMgr::deleteGroup(groupName));
+}
+
+TEST_F(UserMgrInTest, CheckAndThrowForGroupNotExist)
+{
+    EXPECT_THROW(deleteGroup("whatever"),
+                 sdbusplus::xyz::openbmc_project::User::Common::Error::
+                     GroupNameDoesNotExist);
+}
+
+TEST(ReadAllGroupsOnSystemTest, OnlyReturnsPredefinedGroups)
+{
+    EXPECT_THAT(UserMgr::readAllGroupsOnSystem(),
+                testing::UnorderedElementsAre("web", "redfish", "ipmi", "ssh"));
+}
+
 } // namespace user
 } // namespace phosphor