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