blob: f16a9d67468fe2cce679c6f36941a83edcd2bbb2 [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;
256 }
257
258 ~UserMgrInTest() override
259 {
260 EXPECT_NO_THROW(removeFile(tempPamConfigFile));
261 }
262
263 protected:
264 static sdbusplus::bus_t busInTest;
265 std::string tempPamConfigFile;
266};
267
268sdbusplus::bus_t UserMgrInTest::busInTest = sdbusplus::bus::new_default();
269
270TEST_F(UserMgrInTest, GetPamModuleArgValueOnSuccess)
271{
272 std::string minLen;
273 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
274 EXPECT_EQ(minLen, "8");
275 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
276 EXPECT_EQ(minLen, "8");
277}
278
279TEST_F(UserMgrInTest, SetPamModuleArgValueOnSuccess)
280{
281 EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), 0);
282 EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), 0);
283 std::string minLen;
284 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
285 EXPECT_EQ(minLen, "16");
286 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
287 EXPECT_EQ(minLen, "16");
288}
289
290TEST_F(UserMgrInTest, GetPamModuleArgValueOnFailure)
291{
292 EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
293 std::string minLen;
294 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
295 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
296
297 EXPECT_NO_THROW(removeFile(tempPamConfigFile));
298 EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
299 EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
300}
301
302TEST_F(UserMgrInTest, SetPamModuleArgValueOnFailure)
303{
304 EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
305 EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
306 EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
307
308 EXPECT_NO_THROW(removeFile(tempPamConfigFile));
309 EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
310 EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
311}
312
raviteja-b8cc44052019-02-27 23:29:36 -0600313} // namespace user
314} // namespace phosphor