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