blob: f44282e47636bbac59f9e60c03823c4804753eef [file] [log] [blame]
raviteja-b8cc44052019-02-27 23:29:36 -06001#include "mock_user_mgr.hpp"
Nan Zhoue47c09d2022-10-25 00:06:41 +00002#include "user_mgr.hpp"
Patrick Williams9638afb2021-02-22 17:16:24 -06003
Ravi Teja417c0892020-08-22 08:04:01 -05004#include <sdbusplus/test/sdbus_mock.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -06005#include <xyz/openbmc_project/Common/error.hpp>
6#include <xyz/openbmc_project/User/Common/error.hpp>
7
8#include <exception>
Nan Zhoue48085d2022-10-25 00:07:04 +00009#include <filesystem>
10#include <fstream>
Patrick Williams9638afb2021-02-22 17:16:24 -060011
12#include <gtest/gtest.h>
raviteja-b8cc44052019-02-27 23:29:36 -060013
14namespace phosphor
15{
16namespace user
17{
18
19using ::testing::Return;
20
21using InternalFailure =
22 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
23
24class TestUserMgr : public testing::Test
25{
26 public:
Nan Zhou78d85042022-08-29 17:50:22 +000027 sdbusplus::SdBusMock sdBusMock;
Patrick Williamsb3ef4e12022-07-22 19:26:55 -050028 sdbusplus::bus_t bus;
raviteja-b8cc44052019-02-27 23:29:36 -060029 MockManager mockManager;
30
31 TestUserMgr() :
Nan Zhou78d85042022-08-29 17:50:22 +000032 bus(sdbusplus::get_mocked_new(&sdBusMock)), mockManager(bus, objpath)
Patrick Williams9638afb2021-02-22 17:16:24 -060033 {}
raviteja-b8cc44052019-02-27 23:29:36 -060034
Patrick Williams9638afb2021-02-22 17:16:24 -060035 void createLocalUser(const std::string& userName,
raviteja-b8cc44052019-02-27 23:29:36 -060036 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -060037 const std::string& priv, bool enabled)
raviteja-b8cc44052019-02-27 23:29:36 -060038 {
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +053039 sdbusplus::message::object_path tempObjPath(usersObjPath);
40 tempObjPath /= userName;
41 std::string userObj(tempObjPath);
raviteja-b8cc44052019-02-27 23:29:36 -060042 mockManager.usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +000043 userName, std::make_unique<phosphor::user::Users>(
raviteja-b8cc44052019-02-27 23:29:36 -060044 mockManager.bus, userObj.c_str(), groupNames, priv,
Nan Zhou78d85042022-08-29 17:50:22 +000045 enabled, mockManager));
raviteja-b8cc44052019-02-27 23:29:36 -060046 }
47
48 DbusUserObj createPrivilegeMapperDbusObject(void)
49 {
50 DbusUserObj object;
51 DbusUserObjValue objValue;
Ravi Teja5fe724a2019-05-07 05:14:42 -050052
Nan Zhou78d85042022-08-29 17:50:22 +000053 DbusUserObjPath objPath("/xyz/openbmc_project/user/ldap/openldap");
Ravi Teja5fe724a2019-05-07 05:14:42 -050054 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);
Nan Zhou78d85042022-08-29 17:50:22 +000058 object.emplace(objPath, objValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -050059
Nan Zhou78d85042022-08-29 17:50:22 +000060 DbusUserObjPath objectPath(
Ravi Teja5fe724a2019-05-07 05:14:42 -050061 "/xyz/openbmc_project/user/ldap/openldap/role_map/1");
62 std::string group = "ldapGroup";
63 std::string priv = "priv-admin";
raviteja-b8cc44052019-02-27 23:29:36 -060064 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);
Nan Zhou78d85042022-08-29 17:50:22 +000069 object.emplace(objectPath, objValue);
raviteja-b8cc44052019-02-27 23:29:36 -060070
71 return object;
72 }
Ravi Teja5fe724a2019-05-07 05:14:42 -050073
74 DbusUserObj createLdapConfigObjectWithoutPrivilegeMapper(void)
75 {
76 DbusUserObj object;
77 DbusUserObjValue objValue;
78
Nan Zhou78d85042022-08-29 17:50:22 +000079 DbusUserObjPath objPath("/xyz/openbmc_project/user/ldap/openldap");
Ravi Teja5fe724a2019-05-07 05:14:42 -050080 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);
Nan Zhou78d85042022-08-29 17:50:22 +000084 object.emplace(objPath, objValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -050085 return object;
86 }
raviteja-b8cc44052019-02-27 23:29:36 -060087};
88
89TEST_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
99TEST_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"]));
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600115 EXPECT_EQ(false, std::get<bool>(userInfo["UserPasswordExpired"]));
raviteja-b8cc44052019-02-27 23:29:36 -0600116 EXPECT_EQ(false, std::get<bool>(userInfo["RemoteUser"]));
117}
118
119TEST_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
136TEST_F(TestUserMgr, ldapUserWithoutPrivMapper)
137{
138 UserInfoMap userInfo;
139 std::string userName = "ldapUser";
140 std::string ldapGroup = "ldapGroup";
raviteja-b8cc44052019-02-27 23:29:36 -0600141
142 EXPECT_CALL(mockManager, getLdapGroupName(userName))
143 .WillRepeatedly(Return(ldapGroup));
Ravi Teja5fe724a2019-05-07 05:14:42 -0500144 // Create LDAP config object without privilege mapper
145 DbusUserObj object = createLdapConfigObjectWithoutPrivilegeMapper();
raviteja-b8cc44052019-02-27 23:29:36 -0600146 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}
Nan Zhoue47c09d2022-10-25 00:06:41 +0000152
153TEST(GetCSVFromVector, EmptyVectorReturnsEmptyString)
154{
155 EXPECT_EQ(getCSVFromVector({}), "");
156}
157
158TEST(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
Nan Zhou332fb9d2022-10-25 00:07:03 +0000165TEST(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
176TEST(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
Nan Zhoue48085d2022-10-25 00:07:04 +0000188namespace
189{
190inline constexpr const char* objectRootInTest = "/xyz/openbmc_project/user";
191
192// Fake config; referenced config on real BMC
193inline 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)
212password [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 #some comments
213password [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
214password [success=ok default=die] pam_ipmicheck.so spec_grp_name=ipmi use_authtok
215password [success=ok ignore=ignore default=die] pam_pwhistory.so debug enforce_for_root remember=0 use_authtok
216password [success=ok default=die] pam_unix.so sha512 use_authtok
217password [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
219password 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
223password required pam_permit.so
224# and here are more per-package modules (the "Additional" block)
225)";
226} // namespace
227
228void 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
240void removeFile(const std::string& filePath)
241{
242 std::filesystem::remove(filePath);
243}
244
245class 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;
Nan Zhou49c81362022-10-25 00:07:08 +0000256
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));
Nan Zhoue48085d2022-10-25 00:07:04 +0000262 }
263
264 ~UserMgrInTest() override
265 {
266 EXPECT_NO_THROW(removeFile(tempPamConfigFile));
267 }
268
Nan Zhou49c81362022-10-25 00:07:08 +0000269 MOCK_METHOD(void, executeUserAdd, (const char*, const char*, bool, bool),
270 (override));
271
272 MOCK_METHOD(void, executeUserDelete, (const char*), (override));
273
274 MOCK_METHOD(size_t, getIpmiUsersCount, (), (override));
275
Nan Zhoue48085d2022-10-25 00:07:04 +0000276 protected:
277 static sdbusplus::bus_t busInTest;
278 std::string tempPamConfigFile;
279};
280
281sdbusplus::bus_t UserMgrInTest::busInTest = sdbusplus::bus::new_default();
282
283TEST_F(UserMgrInTest, GetPamModuleArgValueOnSuccess)
284{
285 std::string minLen;
286 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
287 EXPECT_EQ(minLen, "8");
288 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
289 EXPECT_EQ(minLen, "8");
290}
291
292TEST_F(UserMgrInTest, SetPamModuleArgValueOnSuccess)
293{
294 EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), 0);
295 EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), 0);
296 std::string minLen;
297 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
298 EXPECT_EQ(minLen, "16");
299 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
300 EXPECT_EQ(minLen, "16");
301}
302
303TEST_F(UserMgrInTest, GetPamModuleArgValueOnFailure)
304{
305 EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
306 std::string minLen;
307 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
308 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
309
310 EXPECT_NO_THROW(removeFile(tempPamConfigFile));
311 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
312 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
313}
314
315TEST_F(UserMgrInTest, SetPamModuleArgValueOnFailure)
316{
317 EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
318 EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
319 EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
320
321 EXPECT_NO_THROW(removeFile(tempPamConfigFile));
322 EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
323 EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
324}
325
Nan Zhou8a11d992022-10-25 00:07:06 +0000326TEST_F(UserMgrInTest, IsUserExistEmptyInputThrowsError)
327{
328 EXPECT_THROW(
329 isUserExist(""),
330 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
331}
332
333TEST_F(UserMgrInTest, ThrowForUserDoesNotExistThrowsError)
334{
335 EXPECT_THROW(throwForUserDoesNotExist("whatever"),
336 sdbusplus::xyz::openbmc_project::User::Common::Error::
337 UserNameDoesNotExist);
338}
339
340TEST_F(UserMgrInTest, ThrowForUserExistsThrowsError)
341{
342 EXPECT_THROW(
343 throwForUserExists("root"),
344 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists);
345}
346
Nan Zhou40e44972022-10-25 00:07:07 +0000347TEST_F(
348 UserMgrInTest,
349 ThrowForUserNameConstraintsExceedIpmiMaxUserNameLenThrowsUserNameGroupFail)
350{
351 std::string strWith17Chars(17, 'A');
352 EXPECT_THROW(throwForUserNameConstraints(strWith17Chars, {"ipmi"}),
353 sdbusplus::xyz::openbmc_project::User::Common::Error::
354 UserNameGroupFail);
355}
356
357TEST_F(
358 UserMgrInTest,
359 ThrowForUserNameConstraintsExceedSystemMaxUserNameLenThrowsInvalidArgument)
360{
361 std::string strWith31Chars(31, 'A');
362 EXPECT_THROW(
363 throwForUserNameConstraints(strWith31Chars, {}),
364 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
365}
366
367TEST_F(UserMgrInTest,
368 ThrowForUserNameConstraintsRegexMismatchThrowsInvalidArgument)
369{
370 std::string startWithNumber = "0ABC";
371 EXPECT_THROW(
372 throwForUserNameConstraints(startWithNumber, {"ipmi"}),
373 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
374}
375
Nan Zhou49c81362022-10-25 00:07:08 +0000376TEST_F(UserMgrInTest, UserAddNotRootFailedWithInternalFailure)
377{
378 EXPECT_THROW(
379 UserMgr::executeUserAdd("user0", "ipmi,ssh", true, true),
380 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
381}
382
383TEST_F(UserMgrInTest, UserDeleteNotRootFailedWithInternalFailure)
384{
385 EXPECT_THROW(
386 UserMgr::executeUserDelete("user0"),
387 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
388}
389
390TEST_F(UserMgrInTest,
391 ThrowForMaxGrpUserCountThrowsNoResourceWhenIpmiUserExceedLimit)
392{
393 EXPECT_CALL(*this, getIpmiUsersCount()).WillOnce(Return(ipmiMaxUsers));
394 EXPECT_THROW(
395 throwForMaxGrpUserCount({"ipmi"}),
396 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource);
397}
398
399TEST_F(UserMgrInTest, CreateUserThrowsInternalFailureWhenExecuteUserAddFails)
400{
401 EXPECT_CALL(*this, executeUserAdd)
402 .WillOnce(testing::Throw(
403 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()));
404 EXPECT_THROW(
405 createUser("whatever", {"redfish"}, "", true),
406 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
407}
408
409TEST_F(UserMgrInTest, DeleteUserThrowsInternalFailureWhenExecuteUserDeleteFails)
410{
411 std::string username = "user";
412 EXPECT_NO_THROW(
413 UserMgr::createUser(username, {"redfish", "ssh"}, "priv-user", true));
414 EXPECT_CALL(*this, executeUserDelete(testing::StrEq(username)))
415 .WillOnce(testing::Throw(
416 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure()))
417 .WillOnce(testing::DoDefault());
418
419 EXPECT_THROW(
420 deleteUser(username),
421 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
422 EXPECT_NO_THROW(UserMgr::deleteUser(username));
423}
424
Nan Zhou589aeb42022-10-25 00:07:09 +0000425TEST_F(UserMgrInTest, ThrowForInvalidPrivilegeThrowsWhenPrivilegeIsInvalid)
426{
427 EXPECT_THROW(
428 throwForInvalidPrivilege("whatever"),
429 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
430}
431
432TEST_F(UserMgrInTest, ThrowForInvalidPrivilegeNoThrowWhenPrivilegeIsValid)
433{
434 EXPECT_NO_THROW(throwForInvalidPrivilege("priv-admin"));
435 EXPECT_NO_THROW(throwForInvalidPrivilege("priv-operator"));
436 EXPECT_NO_THROW(throwForInvalidPrivilege("priv-user"));
437 EXPECT_NO_THROW(throwForInvalidPrivilege("priv-noaccess"));
438}
439
raviteja-b8cc44052019-02-27 23:29:36 -0600440} // namespace user
441} // namespace phosphor