test_user.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. """
  2. tests for user state
  3. user absent
  4. user present
  5. user present with custom homedir
  6. """
  7. import os
  8. import sys
  9. from random import randint
  10. import pytest
  11. import salt.utils.files
  12. import salt.utils.platform
  13. from tests.support.case import ModuleCase
  14. from tests.support.helpers import (
  15. destructiveTest,
  16. requires_system_grains,
  17. skip_if_not_root,
  18. slowTest,
  19. )
  20. from tests.support.mixins import SaltReturnAssertsMixin
  21. from tests.support.unit import skipIf
  22. try:
  23. import grp
  24. except ImportError:
  25. grp = None
  26. if salt.utils.platform.is_darwin():
  27. USER = "macuser"
  28. GROUP = "macuser"
  29. GID = randint(400, 500)
  30. NOGROUPGID = randint(400, 500)
  31. elif salt.utils.platform.is_windows():
  32. USER = "winuser"
  33. GROUP = "winuser"
  34. GID = randint(400, 500)
  35. NOGROUPGID = randint(400, 500)
  36. else:
  37. USER = "nobody"
  38. GROUP = "nobody"
  39. GID = "nobody"
  40. NOGROUPGID = "nogroup"
  41. @destructiveTest
  42. @skip_if_not_root
  43. @pytest.mark.windows_whitelisted
  44. class UserTest(ModuleCase, SaltReturnAssertsMixin):
  45. """
  46. test for user absent
  47. """
  48. user_name = "salt-test"
  49. alt_group = "salt-test-altgroup"
  50. user_home = (
  51. "/var/lib/{}".format(user_name)
  52. if not salt.utils.platform.is_windows()
  53. else os.path.join("tmp", user_name)
  54. )
  55. @slowTest
  56. def test_user_absent(self):
  57. ret = self.run_state("user.absent", name="unpossible")
  58. self.assertSaltTrueReturn(ret)
  59. @slowTest
  60. def test_user_if_present(self):
  61. ret = self.run_state("user.present", name=USER)
  62. self.assertSaltTrueReturn(ret)
  63. @slowTest
  64. def test_user_if_present_with_gid(self):
  65. if self.run_function("group.info", [USER]):
  66. ret = self.run_state("user.present", name=USER, gid=GID)
  67. elif self.run_function("group.info", ["nogroup"]):
  68. ret = self.run_state("user.present", name=USER, gid=NOGROUPGID)
  69. else:
  70. self.skipTest("Neither 'nobody' nor 'nogroup' are valid groups")
  71. self.assertSaltTrueReturn(ret)
  72. @slowTest
  73. def test_user_not_present(self):
  74. """
  75. This is a DESTRUCTIVE TEST it creates a new user on the minion.
  76. And then destroys that user.
  77. Assume that it will break any system you run it on.
  78. """
  79. ret = self.run_state("user.present", name=self.user_name)
  80. self.assertSaltTrueReturn(ret)
  81. @slowTest
  82. def test_user_present_when_home_dir_does_not_18843(self):
  83. """
  84. This is a DESTRUCTIVE TEST it creates a new user on the minion.
  85. And then destroys that user.
  86. Assume that it will break any system you run it on.
  87. """
  88. if salt.utils.platform.is_darwin():
  89. HOMEDIR = "/Users/home_of_" + self.user_name
  90. else:
  91. HOMEDIR = "/home/home_of_" + self.user_name
  92. ret = self.run_state("user.present", name=self.user_name, home=HOMEDIR)
  93. self.assertSaltTrueReturn(ret)
  94. self.run_function("file.absent", name=HOMEDIR)
  95. ret = self.run_state("user.present", name=self.user_name, home=HOMEDIR)
  96. self.assertSaltTrueReturn(ret)
  97. @requires_system_grains
  98. @slowTest
  99. def test_user_present_nondefault(self, grains=None):
  100. """
  101. This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
  102. """
  103. ret = self.run_state("user.present", name=self.user_name, home=self.user_home)
  104. self.assertSaltTrueReturn(ret)
  105. ret = self.run_function("user.info", [self.user_name])
  106. self.assertReturnNonEmptySaltType(ret)
  107. if salt.utils.platform.is_windows():
  108. group_name = self.run_function("user.list_groups", [self.user_name])
  109. else:
  110. group_name = grp.getgrgid(ret["gid"]).gr_name
  111. if not salt.utils.platform.is_darwin() and not salt.utils.platform.is_windows():
  112. self.assertTrue(os.path.isdir(self.user_home))
  113. if grains["os_family"] in ("Suse",):
  114. self.assertEqual(group_name, "users")
  115. elif grains["os_family"] == "MacOS":
  116. self.assertEqual(group_name, "staff")
  117. elif salt.utils.platform.is_windows():
  118. self.assertEqual([], group_name)
  119. else:
  120. self.assertEqual(group_name, self.user_name)
  121. @skipIf(
  122. salt.utils.platform.is_windows(), "windows minion does not support usergroup"
  123. )
  124. @slowTest
  125. def test_user_present_usergroup_false(self):
  126. """
  127. This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
  128. This is a unit test, NOT an integration test. We create a group of the
  129. same name as the user beforehand, so it should all run smoothly.
  130. """
  131. # MacOS users' primary group defaults to staff (20), not the name of
  132. # user
  133. ret = self.run_state("group.present", name=self.user_name)
  134. self.assertSaltTrueReturn(ret)
  135. if salt.utils.platform.is_darwin():
  136. gid = grp.getgrnam("staff").gr_gid
  137. else:
  138. gid = self.user_name
  139. ret = self.run_state(
  140. "user.present",
  141. name=self.user_name,
  142. gid=gid,
  143. usergroup=False,
  144. home=self.user_home,
  145. )
  146. self.assertSaltTrueReturn(ret)
  147. ret = self.run_function("user.info", [self.user_name])
  148. self.assertReturnNonEmptySaltType(ret)
  149. group_name = grp.getgrgid(ret["gid"]).gr_name
  150. if not salt.utils.platform.is_darwin():
  151. self.assertTrue(os.path.isdir(self.user_home))
  152. self.assertEqual(group_name, self.user_name)
  153. ret = self.run_state("user.absent", name=self.user_name)
  154. self.assertSaltTrueReturn(ret)
  155. ret = self.run_state("group.absent", name=self.user_name)
  156. self.assertSaltTrueReturn(ret)
  157. @skipIf(
  158. salt.utils.platform.is_windows(),
  159. "windows minion does not support gid_from_name",
  160. )
  161. @skipIf(
  162. salt.utils.platform.is_windows(), "windows minion does not support usergroup"
  163. )
  164. def test_user_present_usergroup(self):
  165. """
  166. This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
  167. This is a unit test, NOT an integration test.
  168. """
  169. ret = self.run_state(
  170. "user.present", name=self.user_name, usergroup=True, home=self.user_home
  171. )
  172. self.assertSaltTrueReturn(ret)
  173. ret = self.run_function("user.info", [self.user_name])
  174. self.assertReturnNonEmptySaltType(ret)
  175. group_name = grp.getgrgid(ret["gid"]).gr_name
  176. if not salt.utils.platform.is_darwin():
  177. self.assertTrue(os.path.isdir(self.user_home))
  178. if salt.utils.platform.is_darwin():
  179. group_name = "staff"
  180. else:
  181. group_name = self.user_name
  182. self.assertEqual(group_name, group_name)
  183. ret = self.run_state("user.absent", name=self.user_name)
  184. self.assertSaltTrueReturn(ret)
  185. if not salt.utils.platform.is_darwin():
  186. ret = self.run_state("group.absent", name=self.user_name)
  187. self.assertSaltTrueReturn(ret)
  188. @skipIf(
  189. sys.getfilesystemencoding().startswith("ANSI"),
  190. "A system encoding which supports Unicode characters must be set. Current setting is: {}. Try setting $LANG='en_US.UTF-8'".format(
  191. sys.getfilesystemencoding()
  192. ),
  193. )
  194. @slowTest
  195. def test_user_present_unicode(self):
  196. """
  197. This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
  198. It ensures that unicode GECOS data will be properly handled, without
  199. any encoding-related failures.
  200. """
  201. ret = self.run_state(
  202. "user.present",
  203. name=self.user_name,
  204. fullname="Sålt Test",
  205. roomnumber="①②③",
  206. workphone="١٢٣٤",
  207. homephone="६७८",
  208. )
  209. self.assertSaltTrueReturn(ret)
  210. # Ensure updating a user also works
  211. ret = self.run_state(
  212. "user.present",
  213. name=self.user_name,
  214. fullname="Sølt Test",
  215. roomnumber="①③②",
  216. workphone="٣٤١٢",
  217. homephone="६८७",
  218. )
  219. self.assertSaltTrueReturn(ret)
  220. @skipIf(
  221. salt.utils.platform.is_windows(),
  222. "windows minion does not support roomnumber or phone",
  223. )
  224. @slowTest
  225. def test_user_present_gecos(self):
  226. """
  227. This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
  228. It ensures that numeric GECOS data will be properly coerced to strings,
  229. otherwise the state will fail because the GECOS fields are written as
  230. strings (and show up in the user.info output as such). Thus the
  231. comparison will fail, since '12345' != 12345.
  232. """
  233. ret = self.run_state(
  234. "user.present",
  235. name=self.user_name,
  236. fullname=12345,
  237. roomnumber=123,
  238. workphone=1234567890,
  239. homephone=1234567890,
  240. )
  241. self.assertSaltTrueReturn(ret)
  242. @skipIf(
  243. salt.utils.platform.is_windows(),
  244. "windows minion does not support roomnumber or phone",
  245. )
  246. @slowTest
  247. def test_user_present_gecos_empty_fields(self):
  248. """
  249. This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
  250. It ensures that if no GECOS data is supplied, the fields will be coerced
  251. into empty strings as opposed to the string "None".
  252. """
  253. ret = self.run_state(
  254. "user.present",
  255. name=self.user_name,
  256. fullname="",
  257. roomnumber="",
  258. workphone="",
  259. homephone="",
  260. )
  261. self.assertSaltTrueReturn(ret)
  262. ret = self.run_function("user.info", [self.user_name])
  263. self.assertReturnNonEmptySaltType(ret)
  264. self.assertEqual("", ret["fullname"])
  265. # MacOS does not supply the following GECOS fields
  266. if not salt.utils.platform.is_darwin():
  267. self.assertEqual("", ret["roomnumber"])
  268. self.assertEqual("", ret["workphone"])
  269. self.assertEqual("", ret["homephone"])
  270. @skipIf(
  271. salt.utils.platform.is_windows(), "windows minion does not support createhome"
  272. )
  273. @slowTest
  274. def test_user_present_home_directory_created(self):
  275. """
  276. This is a DESTRUCTIVE TEST it creates a new user on the minion.
  277. It ensures that the home directory is created.
  278. """
  279. ret = self.run_state("user.present", name=self.user_name, createhome=True)
  280. self.assertSaltTrueReturn(ret)
  281. user_info = self.run_function("user.info", [self.user_name])
  282. self.assertTrue(os.path.exists(user_info["home"]))
  283. @skipIf(not salt.utils.platform.is_linux(), "only supported on linux")
  284. def test_user_present_gid_from_name(self):
  285. """
  286. Test that gid_from_name results in warning, while it is on a
  287. deprecation path.
  288. """
  289. # Add the user
  290. ret = self.run_state("user.present", name=self.user_name, gid_from_name=True)
  291. self.assertSaltTrueReturn(ret)
  292. ret = ret[next(iter(ret))]
  293. expected = [
  294. "The 'gid_from_name' argument in the user.present state has been "
  295. "replaced with 'usergroup'. Update your SLS file to get rid of "
  296. "this warning."
  297. ]
  298. assert ret["warnings"] == expected, ret["warnings"]
  299. @skipIf(not salt.utils.platform.is_linux(), "only supported on linux")
  300. def test_user_present_gid_from_name_and_usergroup(self):
  301. """
  302. Test that gid_from_name results in warning, while it is on a
  303. deprecation path.
  304. """
  305. # Add the user
  306. ret = self.run_state(
  307. "user.present", name=self.user_name, usergroup=True, gid_from_name=True
  308. )
  309. self.assertSaltTrueReturn(ret)
  310. ret = ret[next(iter(ret))]
  311. expected = [
  312. "The 'gid_from_name' argument in the user.present state has been "
  313. "replaced with 'usergroup'. Ignoring since 'usergroup' was also "
  314. "used."
  315. ]
  316. assert ret["warnings"] == expected, ret["warnings"]
  317. @skipIf(
  318. salt.utils.platform.is_windows() or salt.utils.platform.is_darwin(),
  319. "groups/gid not fully supported",
  320. )
  321. def test_user_present_change_gid_but_keep_group(self):
  322. """
  323. This tests the case in which the default group is changed at the same
  324. time as it is also moved into the "groups" list.
  325. """
  326. try:
  327. # Add the groups
  328. ret = self.run_state("group.present", name=self.user_name)
  329. self.assertSaltTrueReturn(ret)
  330. ret = self.run_state("group.present", name=self.alt_group)
  331. self.assertSaltTrueReturn(ret)
  332. user_name_gid = self.run_function("file.group_to_gid", [self.user_name])
  333. alt_group_gid = self.run_function("file.group_to_gid", [self.alt_group])
  334. # Add the user
  335. ret = self.run_state("user.present", name=self.user_name, gid=alt_group_gid)
  336. # Check that the initial user addition set the gid and groups as
  337. # expected.
  338. new_gid = self.run_function("file.group_to_gid", [self.user_name])
  339. uinfo = self.run_function("user.info", [self.user_name])
  340. assert uinfo["gid"] == alt_group_gid, uinfo["gid"]
  341. assert uinfo["groups"] == [self.alt_group], uinfo["groups"]
  342. # Now change the gid and move alt_group to the groups list in the
  343. # same salt run.
  344. ret = self.run_state(
  345. "user.present",
  346. name=self.user_name,
  347. gid=user_name_gid,
  348. groups=[self.alt_group],
  349. allow_gid_change=True,
  350. )
  351. self.assertSaltTrueReturn(ret)
  352. # Be sure that we did what we intended
  353. new_gid = self.run_function("file.group_to_gid", [self.user_name])
  354. uinfo = self.run_function("user.info", [self.user_name])
  355. assert uinfo["gid"] == new_gid, uinfo["gid"]
  356. assert uinfo["groups"] == [self.user_name, self.alt_group], uinfo["groups"]
  357. finally:
  358. # Do the cleanup here so we don't have to put all of this in the
  359. # tearDown to be executed after each test.
  360. self.assertSaltTrueReturn(
  361. self.run_state("user.absent", name=self.user_name)
  362. )
  363. self.assertSaltTrueReturn(
  364. self.run_state("group.absent", name=self.user_name)
  365. )
  366. self.assertSaltTrueReturn(
  367. self.run_state("group.absent", name=self.alt_group)
  368. )
  369. def tearDown(self):
  370. if salt.utils.platform.is_darwin():
  371. check_user = self.run_function("user.list_users")
  372. if USER in check_user:
  373. del_user = self.run_function("user.delete", [USER], remove=True)
  374. self.assertSaltTrueReturn(self.run_state("user.absent", name=self.user_name))
  375. self.assertSaltTrueReturn(self.run_state("group.absent", name=self.user_name))
  376. @destructiveTest
  377. @skip_if_not_root
  378. @skipIf(not salt.utils.platform.is_windows(), "Windows only tests")
  379. @pytest.mark.windows_whitelisted
  380. class WinUserTest(ModuleCase, SaltReturnAssertsMixin):
  381. """
  382. test for user absent
  383. """
  384. def tearDown(self):
  385. self.assertSaltTrueReturn(self.run_state("user.absent", name=USER))
  386. @slowTest
  387. def test_user_present_existing(self):
  388. ret = self.run_state(
  389. "user.present",
  390. name=USER,
  391. win_homedrive="U:",
  392. win_profile="C:\\User\\{}".format(USER),
  393. win_logonscript="C:\\logon.vbs",
  394. win_description="Test User Account",
  395. )
  396. self.assertSaltTrueReturn(ret)
  397. ret = self.run_state(
  398. "user.present",
  399. name=USER,
  400. win_homedrive="R:",
  401. win_profile="C:\\Users\\{}".format(USER),
  402. win_logonscript="C:\\Windows\\logon.vbs",
  403. win_description="Temporary Account",
  404. )
  405. self.assertSaltTrueReturn(ret)
  406. self.assertSaltStateChangesEqual(ret, "R:", keys=["homedrive"])
  407. self.assertSaltStateChangesEqual(
  408. ret, "C:\\Users\\{}".format(USER), keys=["profile"]
  409. )
  410. self.assertSaltStateChangesEqual(
  411. ret, "C:\\Windows\\logon.vbs", keys=["logonscript"]
  412. )
  413. self.assertSaltStateChangesEqual(ret, "Temporary Account", keys=["description"])