xref: /openbmc/phosphor-user-manager/test/user_mgr_test.cpp (revision 37d26c0ff074f30aebb3b378475f1092dc4367ff)
1 #include "mock_user_mgr.hpp"
2 #include "user_mgr.hpp"
3 
4 #include <sdbusplus/test/sdbus_mock.hpp>
5 #include <xyz/openbmc_project/Common/error.hpp>
6 #include <xyz/openbmc_project/User/Common/error.hpp>
7 
8 #include <exception>
9 #include <filesystem>
10 #include <fstream>
11 
12 #include <gtest/gtest.h>
13 
14 namespace phosphor
15 {
16 namespace user
17 {
18 
19 using ::testing::Return;
20 using ::testing::Throw;
21 
22 using InternalFailure =
23     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
24 using UserNameDoesNotExist =
25     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
26 
27 class TestUserMgr : public testing::Test
28 {
29   public:
30     sdbusplus::SdBusMock sdBusMock;
31     sdbusplus::bus_t bus;
32     MockManager mockManager;
33 
34     TestUserMgr() :
35         bus(sdbusplus::get_mocked_new(&sdBusMock)), mockManager(bus, objpath)
36     {}
37 
38     void createLocalUser(const std::string& userName,
39                          std::vector<std::string> groupNames,
40                          const std::string& priv, bool enabled)
41     {
42         sdbusplus::message::object_path tempObjPath(usersObjPath);
43         tempObjPath /= userName;
44         std::string userObj(tempObjPath);
45         mockManager.usersList.emplace(
46             userName, std::make_unique<phosphor::user::Users>(
47                           mockManager.bus, userObj.c_str(), groupNames, priv,
48                           enabled, mockManager));
49     }
50 
51     DbusUserObj createPrivilegeMapperDbusObject(void)
52     {
53         DbusUserObj object;
54         DbusUserObjValue objValue;
55 
56         DbusUserObjPath objPath("/xyz/openbmc_project/user/ldap/openldap");
57         DbusUserPropVariant enabled(true);
58         DbusUserObjProperties property = {std::make_pair("Enabled", enabled)};
59         std::string intf = "xyz.openbmc_project.Object.Enable";
60         objValue.emplace(intf, property);
61         object.emplace(objPath, objValue);
62 
63         DbusUserObjPath objectPath(
64             "/xyz/openbmc_project/user/ldap/openldap/role_map/1");
65         std::string group = "ldapGroup";
66         std::string priv = "priv-admin";
67         DbusUserObjProperties properties = {std::make_pair("GroupName", group),
68                                             std::make_pair("Privilege", priv)};
69         std::string interface = "xyz.openbmc_project.User.PrivilegeMapperEntry";
70 
71         objValue.emplace(interface, properties);
72         object.emplace(objectPath, objValue);
73 
74         return object;
75     }
76 
77     DbusUserObj createLdapConfigObjectWithoutPrivilegeMapper(void)
78     {
79         DbusUserObj object;
80         DbusUserObjValue objValue;
81 
82         DbusUserObjPath objPath("/xyz/openbmc_project/user/ldap/openldap");
83         DbusUserPropVariant enabled(true);
84         DbusUserObjProperties property = {std::make_pair("Enabled", enabled)};
85         std::string intf = "xyz.openbmc_project.Object.Enable";
86         objValue.emplace(intf, property);
87         object.emplace(objPath, objValue);
88         return object;
89     }
90 };
91 
92 TEST_F(TestUserMgr, ldapEntryDoesNotExist)
93 {
94     std::string userName = "user";
95     UserInfoMap userInfo;
96 
97     EXPECT_CALL(mockManager, getPrimaryGroup(userName))
98         .WillRepeatedly(Throw(UserNameDoesNotExist()));
99     EXPECT_THROW(userInfo = mockManager.getUserInfo(userName),
100                  UserNameDoesNotExist);
101 }
102 
103 TEST_F(TestUserMgr, localUser)
104 {
105     UserInfoMap userInfo;
106     std::string userName = "testUser";
107     std::string privilege = "priv-admin";
108     std::vector<std::string> groups{"testGroup"};
109     // Create local user
110     createLocalUser(userName, groups, privilege, true);
111     EXPECT_CALL(mockManager, userLockedForFailedAttempt(userName)).Times(1);
112     userInfo = mockManager.getUserInfo(userName);
113 
114     EXPECT_EQ(privilege, std::get<std::string>(userInfo["UserPrivilege"]));
115     EXPECT_EQ(groups,
116               std::get<std::vector<std::string>>(userInfo["UserGroups"]));
117     EXPECT_EQ(true, std::get<bool>(userInfo["UserEnabled"]));
118     EXPECT_EQ(false, std::get<bool>(userInfo["UserLockedForFailedAttempt"]));
119     EXPECT_EQ(false, std::get<bool>(userInfo["UserPasswordExpired"]));
120     EXPECT_EQ(false, std::get<bool>(userInfo["RemoteUser"]));
121 }
122 
123 TEST_F(TestUserMgr, ldapUserWithPrivMapper)
124 {
125     UserInfoMap userInfo;
126     std::string userName = "ldapUser";
127     std::string ldapGroup = "ldapGroup";
128     gid_t primaryGid = 1000;
129 
130     EXPECT_CALL(mockManager, getPrimaryGroup(userName))
131         .WillRepeatedly(Return(primaryGid));
132     // Create privilege mapper dbus object
133     DbusUserObj object = createPrivilegeMapperDbusObject();
134     EXPECT_CALL(mockManager, getPrivilegeMapperObject())
135         .WillRepeatedly(Return(object));
136     EXPECT_CALL(mockManager, isGroupMember(userName, primaryGid, ldapGroup))
137         .WillRepeatedly(Return(true));
138     userInfo = mockManager.getUserInfo(userName);
139     EXPECT_EQ(true, std::get<bool>(userInfo["RemoteUser"]));
140     EXPECT_EQ("priv-admin", std::get<std::string>(userInfo["UserPrivilege"]));
141 }
142 
143 TEST_F(TestUserMgr, ldapUserWithoutPrivMapper)
144 {
145     using ::testing::_;
146 
147     UserInfoMap userInfo;
148     std::string userName = "ldapUser";
149     std::string ldapGroup = "ldapGroup";
150     gid_t primaryGid = 1000;
151 
152     EXPECT_CALL(mockManager, getPrimaryGroup(userName))
153         .WillRepeatedly(Return(primaryGid));
154     // Create LDAP config object without privilege mapper
155     DbusUserObj object = createLdapConfigObjectWithoutPrivilegeMapper();
156     EXPECT_CALL(mockManager, getPrivilegeMapperObject())
157         .WillRepeatedly(Return(object));
158     EXPECT_CALL(mockManager, isGroupMember(_, _, _)).Times(0);
159     userInfo = mockManager.getUserInfo(userName);
160     EXPECT_EQ(true, std::get<bool>(userInfo["RemoteUser"]));
161     EXPECT_EQ("", std::get<std::string>(userInfo["UserPrivilege"]));
162 }
163 
164 TEST(GetCSVFromVector, EmptyVectorReturnsEmptyString)
165 {
166     EXPECT_EQ(getCSVFromVector({}), "");
167 }
168 
169 TEST(GetCSVFromVector, ElementsAreJoinedByComma)
170 {
171     EXPECT_EQ(getCSVFromVector(std::vector<std::string>{"123"}), "123");
172     EXPECT_EQ(getCSVFromVector(std::vector<std::string>{"123", "456"}),
173               "123,456");
174 }
175 
176 TEST(RemoveStringFromCSV, WithoutDeleteStringReturnsFalse)
177 {
178     std::string expected = "whatever,https";
179     std::string str = expected;
180     EXPECT_FALSE(removeStringFromCSV(str, "ssh"));
181     EXPECT_EQ(str, expected);
182 
183     std::string empty;
184     EXPECT_FALSE(removeStringFromCSV(empty, "ssh"));
185 }
186 
187 TEST(RemoveStringFromCSV, WithDeleteStringReturnsTrue)
188 {
189     std::string expected = "whatever";
190     std::string str = "whatever,https";
191     EXPECT_TRUE(removeStringFromCSV(str, "https"));
192     EXPECT_EQ(str, expected);
193 
194     str = "https";
195     EXPECT_TRUE(removeStringFromCSV(str, "https"));
196     EXPECT_EQ(str, "");
197 }
198 
199 namespace
200 {
201 inline constexpr const char* objectRootInTest = "/xyz/openbmc_project/user";
202 
203 // Fake config; referenced config on real BMC
204 inline constexpr const char* rawConfig = R"(
205 #
206 # /etc/pam.d/common-password - password-related modules common to all services
207 #
208 # This file is included from other service-specific PAM config files,
209 # and should contain a list of modules that define the services to be
210 # used to change user passwords.  The default is pam_unix.
211 
212 # Explanation of pam_unix options:
213 #
214 # The "sha512" option enables salted SHA512 passwords.  Without this option,
215 # the default is Unix crypt.  Prior releases used the option "md5".
216 #
217 # The "obscure" option replaces the old `OBSCURE_CHECKS_ENAB' option in
218 # login.defs.
219 #
220 # See the pam_unix manpage for other options.
221 
222 # here are the per-package modules (the "Primary" block)
223 password	[success=ok default=die]	pam_tally2.so debug enforce_for_root reject_username minlen=8 difok=0 lcredit=0 ocredit=0 dcredit=0 ucredit=0 deny=2 unlock_time=3 #some comments
224 password	[success=ok default=die]	pam_cracklib.so debug enforce_for_root reject_username minlen=8 difok=0 lcredit=0 ocredit=0 dcredit=0 ucredit=0 #some comments
225 password	[success=ok default=die]	pam_ipmicheck.so spec_grp_name=ipmi use_authtok
226 password	[success=ok ignore=ignore default=die]	pam_pwhistory.so debug enforce_for_root remember=0 use_authtok
227 password	[success=ok default=die]	pam_unix.so sha512 use_authtok
228 password	[success=1 default=die] 	pam_ipmisave.so spec_grp_name=ipmi spec_pass_file=/etc/ipmi_pass key_file=/etc/key_file
229 # here's the fallback if no module succeeds
230 password	requisite			pam_deny.so
231 # prime the stack with a positive return value if there isn't one already;
232 # this avoids us returning an error just because nothing sets a success code
233 # since the modules above will each just jump around
234 password	required			pam_permit.so
235 # and here are more per-package modules (the "Additional" block)
236 )";
237 } // namespace
238 
239 void dumpStringToFile(const std::string& str, const std::string& filePath)
240 {
241     std::ofstream outputFileStream;
242 
243     outputFileStream.exceptions(std::ofstream::failbit | std::ofstream::badbit |
244                                 std::ofstream::eofbit);
245 
246     outputFileStream.open(filePath, std::ios::out);
247     outputFileStream << str << "\n" << std::flush;
248     outputFileStream.close();
249 }
250 
251 void removeFile(const std::string& filePath)
252 {
253     std::filesystem::remove(filePath);
254 }
255 
256 class UserMgrInTest : public testing::Test, public UserMgr
257 {
258   public:
259     UserMgrInTest() : UserMgr(busInTest, objectRootInTest)
260     {
261         tempPamConfigFile = "/tmp/test-data-XXXXXX";
262         mktemp(tempPamConfigFile.data());
263         EXPECT_NO_THROW(dumpStringToFile(rawConfig, tempPamConfigFile));
264         // Set config files to test files
265         pamPasswdConfigFile = tempPamConfigFile;
266         pamAuthConfigFile = tempPamConfigFile;
267 
268         ON_CALL(*this, executeUserAdd).WillByDefault(testing::Return());
269 
270         ON_CALL(*this, executeUserDelete).WillByDefault(testing::Return());
271 
272         ON_CALL(*this, getIpmiUsersCount).WillByDefault(testing::Return(0));
273 
274         ON_CALL(*this, executeUserRename).WillByDefault(testing::Return());
275 
276         ON_CALL(*this, executeUserModify(testing::_, testing::_, testing::_))
277             .WillByDefault(testing::Return());
278 
279         ON_CALL(*this, executeUserModifyUserEnable)
280             .WillByDefault(testing::Return());
281     }
282 
283     ~UserMgrInTest() override
284     {
285         EXPECT_NO_THROW(removeFile(tempPamConfigFile));
286     }
287 
288     MOCK_METHOD(void, executeUserAdd, (const char*, const char*, bool, bool),
289                 (override));
290 
291     MOCK_METHOD(void, executeUserDelete, (const char*), (override));
292 
293     MOCK_METHOD(size_t, getIpmiUsersCount, (), (override));
294 
295     MOCK_METHOD(void, executeUserRename, (const char*, const char*),
296                 (override));
297 
298     MOCK_METHOD(void, executeUserModify, (const char*, const char*, bool),
299                 (override));
300 
301     MOCK_METHOD(void, executeUserModifyUserEnable, (const char*, bool),
302                 (override));
303 
304     MOCK_METHOD(std::vector<std::string>, getFailedAttempt, (const char*),
305                 (override));
306 
307   protected:
308     static sdbusplus::bus_t busInTest;
309     std::string tempPamConfigFile;
310 };
311 
312 sdbusplus::bus_t UserMgrInTest::busInTest = sdbusplus::bus::new_default();
313 
314 TEST_F(UserMgrInTest, GetPamModuleArgValueOnSuccess)
315 {
316     std::string minLen;
317     EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
318     EXPECT_EQ(minLen, "8");
319     EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
320     EXPECT_EQ(minLen, "8");
321 }
322 
323 TEST_F(UserMgrInTest, SetPamModuleArgValueOnSuccess)
324 {
325     EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), 0);
326     EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), 0);
327     std::string minLen;
328     EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
329     EXPECT_EQ(minLen, "16");
330     EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
331     EXPECT_EQ(minLen, "16");
332 }
333 
334 TEST_F(UserMgrInTest, GetPamModuleArgValueOnFailure)
335 {
336     EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
337     std::string minLen;
338     EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
339     EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
340 
341     EXPECT_NO_THROW(removeFile(tempPamConfigFile));
342     EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
343     EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
344 }
345 
346 TEST_F(UserMgrInTest, SetPamModuleArgValueOnFailure)
347 {
348     EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
349     EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
350     EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
351 
352     EXPECT_NO_THROW(removeFile(tempPamConfigFile));
353     EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
354     EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
355 }
356 
357 TEST_F(UserMgrInTest, IsUserExistEmptyInputThrowsError)
358 {
359     EXPECT_THROW(
360         isUserExist(""),
361         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
362 }
363 
364 TEST_F(UserMgrInTest, ThrowForUserDoesNotExistThrowsError)
365 {
366     EXPECT_THROW(throwForUserDoesNotExist("whatever"),
367                  sdbusplus::xyz::openbmc_project::User::Common::Error::
368                      UserNameDoesNotExist);
369 }
370 
371 TEST_F(UserMgrInTest, ThrowForUserExistsThrowsError)
372 {
373     EXPECT_THROW(
374         throwForUserExists("root"),
375         sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists);
376 }
377 
378 TEST_F(
379     UserMgrInTest,
380     ThrowForUserNameConstraintsExceedIpmiMaxUserNameLenThrowsUserNameGroupFail)
381 {
382     std::string strWith17Chars(17, 'A');
383     EXPECT_THROW(throwForUserNameConstraints(strWith17Chars, {"ipmi"}),
384                  sdbusplus::xyz::openbmc_project::User::Common::Error::
385                      UserNameGroupFail);
386 }
387 
388 TEST_F(
389     UserMgrInTest,
390     ThrowForUserNameConstraintsExceedSystemMaxUserNameLenThrowsInvalidArgument)
391 {
392     std::string strWith31Chars(31, 'A');
393     EXPECT_THROW(
394         throwForUserNameConstraints(strWith31Chars, {}),
395         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
396 }
397 
398 TEST_F(UserMgrInTest,
399        ThrowForUserNameConstraintsRegexMismatchThrowsInvalidArgument)
400 {
401     std::string startWithNumber = "0ABC";
402     EXPECT_THROW(
403         throwForUserNameConstraints(startWithNumber, {"ipmi"}),
404         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
405 }
406 
407 TEST_F(UserMgrInTest, UserAddNotRootFailedWithInternalFailure)
408 {
409     EXPECT_THROW(
410         UserMgr::executeUserAdd("user0", "ipmi,ssh", true, true),
411         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
412 }
413 
414 TEST_F(UserMgrInTest, UserDeleteNotRootFailedWithInternalFailure)
415 {
416     EXPECT_THROW(
417         UserMgr::executeUserDelete("user0"),
418         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
419 }
420 
421 TEST_F(UserMgrInTest,
422        ThrowForMaxGrpUserCountThrowsNoResourceWhenIpmiUserExceedLimit)
423 {
424     EXPECT_CALL(*this, getIpmiUsersCount()).WillOnce(Return(ipmiMaxUsers));
425     EXPECT_THROW(
426         throwForMaxGrpUserCount({"ipmi"}),
427         sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource);
428 }
429 
430 TEST_F(UserMgrInTest, CreateUserThrowsInternalFailureWhenExecuteUserAddFails)
431 {
432     EXPECT_CALL(*this, executeUserAdd)
433         .WillOnce(testing::Throw(
434             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
435     EXPECT_THROW(
436         createUser("whatever", {"redfish"}, "", true),
437         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
438 }
439 
440 TEST_F(UserMgrInTest, DeleteUserThrowsInternalFailureWhenExecuteUserDeleteFails)
441 {
442     std::string username = "user";
443     EXPECT_NO_THROW(
444         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
445     EXPECT_CALL(*this, executeUserDelete(testing::StrEq(username)))
446         .WillOnce(testing::Throw(
447             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()))
448         .WillOnce(testing::DoDefault());
449 
450     EXPECT_THROW(
451         deleteUser(username),
452         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
453     EXPECT_NO_THROW(UserMgr::deleteUser(username));
454 }
455 
456 TEST_F(UserMgrInTest, ThrowForInvalidPrivilegeThrowsWhenPrivilegeIsInvalid)
457 {
458     EXPECT_THROW(
459         throwForInvalidPrivilege("whatever"),
460         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
461 }
462 
463 TEST_F(UserMgrInTest, ThrowForInvalidPrivilegeNoThrowWhenPrivilegeIsValid)
464 {
465     EXPECT_NO_THROW(throwForInvalidPrivilege("priv-admin"));
466     EXPECT_NO_THROW(throwForInvalidPrivilege("priv-operator"));
467     EXPECT_NO_THROW(throwForInvalidPrivilege("priv-user"));
468 }
469 
470 TEST_F(UserMgrInTest, ThrowForInvalidGroupsThrowsWhenGroupIsInvalid)
471 {
472     EXPECT_THROW(
473         throwForInvalidGroups({"whatever"}),
474         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
475 }
476 
477 TEST_F(UserMgrInTest, ThrowForInvalidGroupsNoThrowWhenGroupIsValid)
478 {
479     EXPECT_NO_THROW(throwForInvalidGroups({"ipmi"}));
480     EXPECT_NO_THROW(throwForInvalidGroups({"ssh"}));
481     EXPECT_NO_THROW(throwForInvalidGroups({"redfish"}));
482     EXPECT_NO_THROW(throwForInvalidGroups({"web"}));
483 }
484 
485 TEST_F(UserMgrInTest, RenameUserOnSuccess)
486 {
487     std::string username = "user001";
488     EXPECT_NO_THROW(
489         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
490     std::string newUsername = "user002";
491 
492     EXPECT_NO_THROW(UserMgr::renameUser(username, newUsername));
493 
494     // old username doesn't exist
495     EXPECT_THROW(getUserInfo(username),
496                  sdbusplus::xyz::openbmc_project::User::Common::Error::
497                      UserNameDoesNotExist);
498 
499     UserInfoMap userInfo = getUserInfo(newUsername);
500     EXPECT_EQ(std::get<Privilege>(userInfo["UserPrivilege"]), "priv-user");
501     EXPECT_THAT(std::get<GroupList>(userInfo["UserGroups"]),
502                 testing::UnorderedElementsAre("redfish", "ssh"));
503     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), true);
504 
505     EXPECT_NO_THROW(UserMgr::deleteUser(newUsername));
506 }
507 
508 TEST_F(UserMgrInTest, RenameUserThrowsInternalFailureIfExecuteUserModifyFails)
509 {
510     std::string username = "user001";
511     EXPECT_NO_THROW(
512         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
513     std::string newUsername = "user002";
514 
515     EXPECT_CALL(*this, executeUserRename(testing::StrEq(username),
516                                          testing::StrEq(newUsername)))
517         .WillOnce(testing::Throw(
518             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
519     EXPECT_THROW(
520         UserMgr::renameUser(username, newUsername),
521         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
522 
523     // The original user is unchanged
524     UserInfoMap userInfo = getUserInfo(username);
525     EXPECT_EQ(std::get<Privilege>(userInfo["UserPrivilege"]), "priv-user");
526     EXPECT_THAT(std::get<GroupList>(userInfo["UserGroups"]),
527                 testing::UnorderedElementsAre("redfish", "ssh"));
528     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), true);
529 
530     EXPECT_NO_THROW(UserMgr::deleteUser(username));
531 }
532 
533 TEST_F(UserMgrInTest, DefaultUserModifyFailedWithInternalFailure)
534 {
535     EXPECT_THROW(
536         UserMgr::executeUserRename("user0", "user1"),
537         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
538     EXPECT_THROW(
539         UserMgr::executeUserModify("user0", "ssh", true),
540         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
541 }
542 
543 TEST_F(UserMgrInTest, UpdateGroupsAndPrivOnSuccess)
544 {
545     std::string username = "user001";
546     EXPECT_NO_THROW(
547         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
548     EXPECT_NO_THROW(
549         updateGroupsAndPriv(username, {"ipmi", "ssh"}, "priv-admin"));
550     UserInfoMap userInfo = getUserInfo(username);
551     EXPECT_EQ(std::get<Privilege>(userInfo["UserPrivilege"]), "priv-admin");
552     EXPECT_THAT(std::get<GroupList>(userInfo["UserGroups"]),
553                 testing::UnorderedElementsAre("ipmi", "ssh"));
554     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), true);
555     EXPECT_NO_THROW(UserMgr::deleteUser(username));
556 }
557 
558 TEST_F(UserMgrInTest,
559        UpdateGroupsAndPrivThrowsInternalFailureIfExecuteUserModifyFail)
560 {
561     std::string username = "user001";
562     EXPECT_NO_THROW(
563         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
564     EXPECT_CALL(*this, executeUserModify(testing::StrEq(username), testing::_,
565                                          testing::_))
566         .WillOnce(testing::Throw(
567             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
568     EXPECT_THROW(
569         updateGroupsAndPriv(username, {"ipmi", "ssh"}, "priv-admin"),
570         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
571     EXPECT_NO_THROW(UserMgr::deleteUser(username));
572 }
573 
574 TEST_F(UserMgrInTest, MinPasswordLengthReturnsIfValueIsTheSame)
575 {
576     initializeAccountPolicy();
577     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
578     UserMgr::minPasswordLength(8);
579     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
580 }
581 
582 TEST_F(UserMgrInTest,
583        MinPasswordLengthRejectsTooShortPasswordWithInvalidArgument)
584 {
585     initializeAccountPolicy();
586     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
587     EXPECT_THROW(
588         UserMgr::minPasswordLength(minPasswdLength - 1),
589         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
590     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
591 }
592 
593 TEST_F(UserMgrInTest, MinPasswordLengthOnSuccess)
594 {
595     initializeAccountPolicy();
596     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
597     UserMgr::minPasswordLength(16);
598     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 16);
599 }
600 
601 TEST_F(UserMgrInTest, MinPasswordLengthOnFailure)
602 {
603     EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
604     initializeAccountPolicy();
605     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
606     EXPECT_THROW(
607         UserMgr::minPasswordLength(16),
608         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
609     EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
610 }
611 
612 TEST_F(UserMgrInTest, RememberOldPasswordTimesReturnsIfValueIsTheSame)
613 {
614     initializeAccountPolicy();
615     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
616     UserMgr::rememberOldPasswordTimes(8);
617     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 8);
618     UserMgr::rememberOldPasswordTimes(8);
619     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 8);
620 }
621 
622 TEST_F(UserMgrInTest, RememberOldPasswordTimesOnSuccess)
623 {
624     initializeAccountPolicy();
625     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
626     UserMgr::rememberOldPasswordTimes(16);
627     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 16);
628 }
629 
630 TEST_F(UserMgrInTest, RememberOldPasswordTimesOnFailure)
631 {
632     EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
633     initializeAccountPolicy();
634     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
635     EXPECT_THROW(
636         UserMgr::rememberOldPasswordTimes(16),
637         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
638     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
639 }
640 
641 TEST_F(UserMgrInTest, MaxLoginAttemptBeforeLockoutReturnsIfValueIsTheSame)
642 {
643     initializeAccountPolicy();
644     EXPECT_EQ(AccountPolicyIface::maxLoginAttemptBeforeLockout(), 2);
645     UserMgr::maxLoginAttemptBeforeLockout(2);
646     EXPECT_EQ(AccountPolicyIface::maxLoginAttemptBeforeLockout(), 2);
647 }
648 
649 TEST_F(UserMgrInTest, MaxLoginAttemptBeforeLockoutOnSuccess)
650 {
651     initializeAccountPolicy();
652     EXPECT_EQ(AccountPolicyIface::maxLoginAttemptBeforeLockout(), 2);
653     UserMgr::maxLoginAttemptBeforeLockout(16);
654     EXPECT_EQ(AccountPolicyIface::maxLoginAttemptBeforeLockout(), 16);
655 }
656 
657 TEST_F(UserMgrInTest, MaxLoginAttemptBeforeLockoutOnFailure)
658 {
659     EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
660     initializeAccountPolicy();
661     EXPECT_THROW(
662         UserMgr::maxLoginAttemptBeforeLockout(16),
663         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
664     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
665 }
666 
667 TEST_F(UserMgrInTest, AccountUnlockTimeoutReturnsIfValueIsTheSame)
668 {
669     initializeAccountPolicy();
670     EXPECT_EQ(AccountPolicyIface::accountUnlockTimeout(), 3);
671     UserMgr::accountUnlockTimeout(3);
672     EXPECT_EQ(AccountPolicyIface::accountUnlockTimeout(), 3);
673 }
674 
675 TEST_F(UserMgrInTest, AccountUnlockTimeoutOnSuccess)
676 {
677     initializeAccountPolicy();
678     EXPECT_EQ(AccountPolicyIface::accountUnlockTimeout(), 3);
679     UserMgr::accountUnlockTimeout(16);
680     EXPECT_EQ(AccountPolicyIface::accountUnlockTimeout(), 16);
681 }
682 
683 TEST_F(UserMgrInTest, AccountUnlockTimeoutOnFailure)
684 {
685     initializeAccountPolicy();
686     EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
687     EXPECT_THROW(
688         UserMgr::accountUnlockTimeout(16),
689         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
690     EXPECT_EQ(AccountPolicyIface::accountUnlockTimeout(), 3);
691 }
692 
693 TEST_F(UserMgrInTest, UserEnableOnSuccess)
694 {
695     std::string username = "user001";
696     EXPECT_NO_THROW(
697         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
698     UserInfoMap userInfo = getUserInfo(username);
699     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), true);
700 
701     EXPECT_NO_THROW(userEnable(username, false));
702 
703     userInfo = getUserInfo(username);
704     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), false);
705 
706     EXPECT_NO_THROW(UserMgr::deleteUser(username));
707 }
708 
709 TEST_F(UserMgrInTest, UserEnableThrowsInternalFailureIfExecuteUserModifyFail)
710 {
711     std::string username = "user001";
712     EXPECT_NO_THROW(
713         UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
714     UserInfoMap userInfo = getUserInfo(username);
715     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), true);
716 
717     EXPECT_CALL(*this, executeUserModifyUserEnable(testing::StrEq(username),
718                                                    testing::Eq(false)))
719         .WillOnce(testing::Throw(
720             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
721     EXPECT_THROW(
722         userEnable(username, false),
723         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
724 
725     userInfo = getUserInfo(username);
726     // Stay unchanged
727     EXPECT_EQ(std::get<UserEnabled>(userInfo["UserEnabled"]), true);
728 
729     EXPECT_NO_THROW(UserMgr::deleteUser(username));
730 }
731 
732 TEST_F(
733     UserMgrInTest,
734     UserLockedForFailedAttemptReturnsFalseIfMaxLoginAttemptBeforeLockoutIsZero)
735 {
736     EXPECT_FALSE(userLockedForFailedAttempt("whatever"));
737 }
738 
739 TEST_F(UserMgrInTest, UserLockedForFailedAttemptZeroFailuresReturnsFalse)
740 {
741     std::string username = "user001";
742     initializeAccountPolicy();
743     // Example output from BMC
744     // root@s7106:~# pam_tally2 -u root
745     // Login           Failures Latest failure     From
746     // root                0
747     std::vector<std::string> output = {"whatever", "root\t0"};
748     EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
749         .WillOnce(testing::Return(output));
750 
751     EXPECT_FALSE(userLockedForFailedAttempt(username));
752 }
753 
754 TEST_F(UserMgrInTest, UserLockedForFailedAttemptFailIfGetFailedAttemptFail)
755 {
756     std::string username = "user001";
757     initializeAccountPolicy();
758     EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
759         .WillOnce(testing::Throw(
760             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
761 
762     EXPECT_THROW(
763         userLockedForFailedAttempt(username),
764         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
765 }
766 
767 TEST_F(UserMgrInTest,
768        UserLockedForFailedAttemptThrowsInternalFailureIfFailAttemptsOutOfRange)
769 {
770     std::string username = "user001";
771     initializeAccountPolicy();
772     std::vector<std::string> output = {"whatever", "root\t1000000"};
773     EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
774         .WillOnce(testing::Return(output));
775 
776     EXPECT_THROW(
777         userLockedForFailedAttempt(username),
778         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
779 }
780 
781 TEST_F(UserMgrInTest,
782        UserLockedForFailedAttemptThrowsInternalFailureIfNoFailDateTime)
783 {
784     std::string username = "user001";
785     initializeAccountPolicy();
786     std::vector<std::string> output = {"whatever", "root\t2"};
787     EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
788         .WillOnce(testing::Return(output));
789 
790     EXPECT_THROW(
791         userLockedForFailedAttempt(username),
792         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
793 }
794 
795 TEST_F(UserMgrInTest,
796        UserLockedForFailedAttemptThrowsInternalFailureIfWrongDateFormat)
797 {
798     std::string username = "user001";
799     initializeAccountPolicy();
800 
801     // Choose a date in the past.
802     std::vector<std::string> output = {"whatever",
803                                        "root\t2\t10/24/2002\t00:00:00"};
804     EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
805         .WillOnce(testing::Return(output));
806 
807     EXPECT_THROW(
808         userLockedForFailedAttempt(username),
809         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
810 }
811 
812 TEST_F(UserMgrInTest,
813        UserLockedForFailedAttemptReturnsFalseIfLastFailTimeHasTimedOut)
814 {
815     std::string username = "user001";
816     initializeAccountPolicy();
817 
818     // Choose a date in the past.
819     std::vector<std::string> output = {"whatever",
820                                        "root\t2\t10/24/02\t00:00:00"};
821     EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
822         .WillOnce(testing::Return(output));
823 
824     EXPECT_EQ(userLockedForFailedAttempt(username), false);
825 }
826 
827 } // namespace user
828 } // namespace phosphor
829