test_fakepwd.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.python.fakepwd}.
  5. """
  6. try:
  7. import pwd
  8. except ImportError:
  9. pwd = None
  10. try:
  11. import spwd
  12. except ImportError:
  13. spwd = None
  14. import os
  15. from operator import getitem
  16. from twisted.trial.unittest import TestCase
  17. from twisted.python.fakepwd import UserDatabase, ShadowDatabase
  18. SYSTEM_UID_MAX = 999
  19. def findInvalidUID():
  20. """
  21. By convention, UIDs less than 1000 are reserved for the system. A system
  22. which allocated every single one of those UIDs would likely have practical
  23. problems with allocating new ones, so let's assume that we'll be able to
  24. find one. (If we don't, this will wrap around to negative values and
  25. I{eventually} find something.)
  26. @return: a user ID which does not exist on the local system. Or, on
  27. systems without a L{pwd} module, return C{SYSTEM_UID_MAX}.
  28. """
  29. guess = SYSTEM_UID_MAX
  30. if pwd is not None:
  31. while True:
  32. try:
  33. pwd.getpwuid(guess)
  34. except KeyError:
  35. break
  36. else:
  37. guess -= 1
  38. return guess
  39. INVALID_UID = findInvalidUID()
  40. class UserDatabaseTestsMixin(object):
  41. """
  42. L{UserDatabaseTestsMixin} defines tests which apply to any user database
  43. implementation. Subclasses should mix it in, implement C{setUp} to create
  44. C{self.database} bound to a user database instance, and implement
  45. C{getExistingUserInfo} to return information about a user (such information
  46. should be unique per test method).
  47. """
  48. def test_getpwuid(self):
  49. """
  50. I{getpwuid} accepts a uid and returns the user record associated with
  51. it.
  52. """
  53. for i in range(2):
  54. # Get some user which exists in the database.
  55. username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
  56. # Now try to look it up and make sure the result is correct.
  57. entry = self.database.getpwuid(uid)
  58. self.assertEqual(entry.pw_name, username)
  59. self.assertEqual(entry.pw_passwd, password)
  60. self.assertEqual(entry.pw_uid, uid)
  61. self.assertEqual(entry.pw_gid, gid)
  62. self.assertEqual(entry.pw_gecos, gecos)
  63. self.assertEqual(entry.pw_dir, dir)
  64. self.assertEqual(entry.pw_shell, shell)
  65. def test_noSuchUID(self):
  66. """
  67. I{getpwuid} raises L{KeyError} when passed a uid which does not exist
  68. in the user database.
  69. """
  70. self.assertRaises(KeyError, self.database.getpwuid, INVALID_UID)
  71. def test_getpwnam(self):
  72. """
  73. I{getpwnam} accepts a username and returns the user record associated
  74. with it.
  75. """
  76. for i in range(2):
  77. # Get some user which exists in the database.
  78. username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
  79. # Now try to look it up and make sure the result is correct.
  80. entry = self.database.getpwnam(username)
  81. self.assertEqual(entry.pw_name, username)
  82. self.assertEqual(entry.pw_passwd, password)
  83. self.assertEqual(entry.pw_uid, uid)
  84. self.assertEqual(entry.pw_gid, gid)
  85. self.assertEqual(entry.pw_gecos, gecos)
  86. self.assertEqual(entry.pw_dir, dir)
  87. self.assertEqual(entry.pw_shell, shell)
  88. def test_noSuchName(self):
  89. """
  90. I{getpwnam} raises L{KeyError} when passed a username which does not
  91. exist in the user database.
  92. """
  93. self.assertRaises(
  94. KeyError, self.database.getpwnam,
  95. 'no' 'such' 'user' 'exists' 'the' 'name' 'is' 'too' 'long' 'and' 'has'
  96. '\1' 'in' 'it' 'too')
  97. def test_recordLength(self):
  98. """
  99. The user record returned by I{getpwuid}, I{getpwnam}, and I{getpwall}
  100. has a length.
  101. """
  102. db = self.database
  103. username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
  104. for entry in [db.getpwuid(uid), db.getpwnam(username), db.getpwall()[0]]:
  105. self.assertIsInstance(len(entry), int)
  106. self.assertEqual(len(entry), 7)
  107. def test_recordIndexable(self):
  108. """
  109. The user record returned by I{getpwuid}, I{getpwnam}, and I{getpwall}
  110. is indexable, with successive indexes starting from 0 corresponding to
  111. the values of the C{pw_name}, C{pw_passwd}, C{pw_uid}, C{pw_gid},
  112. C{pw_gecos}, C{pw_dir}, and C{pw_shell} attributes, respectively.
  113. """
  114. db = self.database
  115. username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
  116. for entry in [db.getpwuid(uid), db.getpwnam(username), db.getpwall()[0]]:
  117. self.assertEqual(entry[0], username)
  118. self.assertEqual(entry[1], password)
  119. self.assertEqual(entry[2], uid)
  120. self.assertEqual(entry[3], gid)
  121. self.assertEqual(entry[4], gecos)
  122. self.assertEqual(entry[5], dir)
  123. self.assertEqual(entry[6], shell)
  124. self.assertEqual(len(entry), len(list(entry)))
  125. self.assertRaises(IndexError, getitem, entry, 7)
  126. class UserDatabaseTests(TestCase, UserDatabaseTestsMixin):
  127. """
  128. Tests for L{UserDatabase}.
  129. """
  130. def setUp(self):
  131. """
  132. Create a L{UserDatabase} with no user data in it.
  133. """
  134. self.database = UserDatabase()
  135. self._counter = SYSTEM_UID_MAX + 1
  136. def getExistingUserInfo(self):
  137. """
  138. Add a new user to C{self.database} and return its information.
  139. """
  140. self._counter += 1
  141. suffix = '_' + str(self._counter)
  142. username = 'username' + suffix
  143. password = 'password' + suffix
  144. uid = self._counter
  145. gid = self._counter + 1000
  146. gecos = 'gecos' + suffix
  147. dir = 'dir' + suffix
  148. shell = 'shell' + suffix
  149. self.database.addUser(username, password, uid, gid, gecos, dir, shell)
  150. return (username, password, uid, gid, gecos, dir, shell)
  151. def test_addUser(self):
  152. """
  153. L{UserDatabase.addUser} accepts seven arguments, one for each field of
  154. a L{pwd.struct_passwd}, and makes the new record available via
  155. L{UserDatabase.getpwuid}, L{UserDatabase.getpwnam}, and
  156. L{UserDatabase.getpwall}.
  157. """
  158. username = 'alice'
  159. password = 'secr3t'
  160. uid = 123
  161. gid = 456
  162. gecos = 'Alice,,,'
  163. home = '/users/alice'
  164. shell = '/usr/bin/foosh'
  165. db = self.database
  166. db.addUser(username, password, uid, gid, gecos, home, shell)
  167. for [entry] in [[db.getpwuid(uid)], [db.getpwnam(username)],
  168. db.getpwall()]:
  169. self.assertEqual(entry.pw_name, username)
  170. self.assertEqual(entry.pw_passwd, password)
  171. self.assertEqual(entry.pw_uid, uid)
  172. self.assertEqual(entry.pw_gid, gid)
  173. self.assertEqual(entry.pw_gecos, gecos)
  174. self.assertEqual(entry.pw_dir, home)
  175. self.assertEqual(entry.pw_shell, shell)
  176. class PwdModuleTests(TestCase, UserDatabaseTestsMixin):
  177. """
  178. L{PwdModuleTests} runs the tests defined by L{UserDatabaseTestsMixin}
  179. against the built-in C{pwd} module. This serves to verify that
  180. L{UserDatabase} is really a fake of that API.
  181. """
  182. if pwd is None:
  183. skip = "Cannot verify UserDatabase against pwd without pwd"
  184. else:
  185. database = pwd
  186. def setUp(self):
  187. self._users = iter(self.database.getpwall())
  188. self._uids = set()
  189. def getExistingUserInfo(self):
  190. """
  191. Read and return the next record from C{self._users}, filtering out
  192. any records with previously seen uid values (as these cannot be
  193. found with C{getpwuid} and only cause trouble).
  194. """
  195. while True:
  196. entry = next(self._users)
  197. uid = entry.pw_uid
  198. if uid not in self._uids:
  199. self._uids.add(uid)
  200. return entry
  201. class ShadowDatabaseTestsMixin(object):
  202. """
  203. L{ShadowDatabaseTestsMixin} defines tests which apply to any shadow user
  204. database implementation. Subclasses should mix it in, implement C{setUp} to
  205. create C{self.database} bound to a shadow user database instance, and
  206. implement C{getExistingUserInfo} to return information about a user (such
  207. information should be unique per test method).
  208. """
  209. def test_getspnam(self):
  210. """
  211. L{getspnam} accepts a username and returns the user record associated
  212. with it.
  213. """
  214. for i in range(2):
  215. # Get some user which exists in the database.
  216. (username, password, lastChange, min, max, warn, inact, expire,
  217. flag) = self.getExistingUserInfo()
  218. entry = self.database.getspnam(username)
  219. self.assertEqual(entry.sp_nam, username)
  220. self.assertEqual(entry.sp_pwd, password)
  221. self.assertEqual(entry.sp_lstchg, lastChange)
  222. self.assertEqual(entry.sp_min, min)
  223. self.assertEqual(entry.sp_max, max)
  224. self.assertEqual(entry.sp_warn, warn)
  225. self.assertEqual(entry.sp_inact, inact)
  226. self.assertEqual(entry.sp_expire, expire)
  227. self.assertEqual(entry.sp_flag, flag)
  228. def test_noSuchName(self):
  229. """
  230. I{getspnam} raises L{KeyError} when passed a username which does not
  231. exist in the user database.
  232. """
  233. self.assertRaises(KeyError, self.database.getspnam, "alice")
  234. def test_recordLength(self):
  235. """
  236. The shadow user record returned by I{getspnam} and I{getspall} has a
  237. length.
  238. """
  239. db = self.database
  240. username = self.getExistingUserInfo()[0]
  241. for entry in [db.getspnam(username), db.getspall()[0]]:
  242. self.assertIsInstance(len(entry), int)
  243. self.assertEqual(len(entry), 9)
  244. def test_recordIndexable(self):
  245. """
  246. The shadow user record returned by I{getpwnam} and I{getspall} is
  247. indexable, with successive indexes starting from 0 corresponding to the
  248. values of the C{sp_nam}, C{sp_pwd}, C{sp_lstchg}, C{sp_min}, C{sp_max},
  249. C{sp_warn}, C{sp_inact}, C{sp_expire}, and C{sp_flag} attributes,
  250. respectively.
  251. """
  252. db = self.database
  253. (username, password, lastChange, min, max, warn, inact, expire,
  254. flag) = self.getExistingUserInfo()
  255. for entry in [db.getspnam(username), db.getspall()[0]]:
  256. self.assertEqual(entry[0], username)
  257. self.assertEqual(entry[1], password)
  258. self.assertEqual(entry[2], lastChange)
  259. self.assertEqual(entry[3], min)
  260. self.assertEqual(entry[4], max)
  261. self.assertEqual(entry[5], warn)
  262. self.assertEqual(entry[6], inact)
  263. self.assertEqual(entry[7], expire)
  264. self.assertEqual(entry[8], flag)
  265. self.assertEqual(len(entry), len(list(entry)))
  266. self.assertRaises(IndexError, getitem, entry, 9)
  267. class ShadowDatabaseTests(TestCase, ShadowDatabaseTestsMixin):
  268. """
  269. Tests for L{ShadowDatabase}.
  270. """
  271. def setUp(self):
  272. """
  273. Create a L{ShadowDatabase} with no user data in it.
  274. """
  275. self.database = ShadowDatabase()
  276. self._counter = 0
  277. def getExistingUserInfo(self):
  278. """
  279. Add a new user to C{self.database} and return its information.
  280. """
  281. self._counter += 1
  282. suffix = '_' + str(self._counter)
  283. username = 'username' + suffix
  284. password = 'password' + suffix
  285. lastChange = self._counter + 1
  286. min = self._counter + 2
  287. max = self._counter + 3
  288. warn = self._counter + 4
  289. inact = self._counter + 5
  290. expire = self._counter + 6
  291. flag = self._counter + 7
  292. self.database.addUser(username, password, lastChange, min, max, warn,
  293. inact, expire, flag)
  294. return (username, password, lastChange, min, max, warn, inact,
  295. expire, flag)
  296. def test_addUser(self):
  297. """
  298. L{UserDatabase.addUser} accepts seven arguments, one for each field of
  299. a L{pwd.struct_passwd}, and makes the new record available via
  300. L{UserDatabase.getpwuid}, L{UserDatabase.getpwnam}, and
  301. L{UserDatabase.getpwall}.
  302. """
  303. username = 'alice'
  304. password = 'secr3t'
  305. lastChange = 17
  306. min = 42
  307. max = 105
  308. warn = 12
  309. inact = 3
  310. expire = 400
  311. flag = 3
  312. db = self.database
  313. db.addUser(username, password, lastChange, min, max, warn, inact,
  314. expire, flag)
  315. for [entry] in [[db.getspnam(username)], db.getspall()]:
  316. self.assertEqual(entry.sp_nam, username)
  317. self.assertEqual(entry.sp_pwd, password)
  318. self.assertEqual(entry.sp_lstchg, lastChange)
  319. self.assertEqual(entry.sp_min, min)
  320. self.assertEqual(entry.sp_max, max)
  321. self.assertEqual(entry.sp_warn, warn)
  322. self.assertEqual(entry.sp_inact, inact)
  323. self.assertEqual(entry.sp_expire, expire)
  324. self.assertEqual(entry.sp_flag, flag)
  325. class SPwdModuleTests(TestCase, ShadowDatabaseTestsMixin):
  326. """
  327. L{SPwdModuleTests} runs the tests defined by L{ShadowDatabaseTestsMixin}
  328. against the built-in C{spwd} module. This serves to verify that
  329. L{ShadowDatabase} is really a fake of that API.
  330. """
  331. if spwd is None:
  332. skip = "Cannot verify ShadowDatabase against spwd without spwd"
  333. elif os.getuid() != 0:
  334. skip = "Cannot access shadow user database except as root"
  335. else:
  336. database = spwd
  337. def setUp(self):
  338. self._users = iter(self.database.getspall())
  339. def getExistingUserInfo(self):
  340. """
  341. Read and return the next record from C{self._users}.
  342. """
  343. return next(self._users)