Ticket #31738: test_fakepwd.2.py

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