]> git.k1024.org Git - pylibacl.git/blob - test/test_acls.py
Try to fix uid_t/gid_t usage in entry qualifiers
[pylibacl.git] / test / test_acls.py
1 #
2 #
3
4 """Unittests for the posix1e module"""
5
6 #  Copyright (C) 2002-2009, 2012, 2014 Iustin Pop <iusty@k1024.org>
7 #
8 #  This library is free software; you can redistribute it and/or
9 #  modify it under the terms of the GNU Lesser General Public
10 #  License as published by the Free Software Foundation; either
11 #  version 2.1 of the License, or (at your option) any later version.
12 #
13 #  This library is distributed in the hope that it will be useful,
14 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 #  Lesser General Public License for more details.
17 #
18 #  You should have received a copy of the GNU Lesser General Public
19 #  License along with this library; if not, write to the Free Software
20 #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 #  02110-1301  USA
22
23
24 import unittest
25 import os
26 import tempfile
27 import sys
28 import platform
29 import re
30
31 import posix1e
32 from posix1e import *
33
34 TEST_DIR = os.environ.get("TEST_DIR", ".")
35
36 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
37
38 # This is to workaround python 2/3 differences at syntactic level
39 # (which can't be worked around via if's)
40 M0500 = 320 # octal 0500
41 M0644 = 420 # octal 0644
42 M0755 = 493 # octal 755
43
44 # Check if running under Python 3
45 IS_PY_3K = sys.hexversion >= 0x03000000
46
47 def _skip_test(fn):
48     """Wrapper to skip a test"""
49     new_fn = lambda x: None
50     new_fn.__doc__ = "SKIPPED %s" % fn.__doc__
51     return new_fn
52
53
54 def has_ext(extension):
55     """Decorator to skip tests based on platform support"""
56     if not extension:
57         return _skip_test
58     else:
59         return lambda x: x
60
61
62 class aclTest:
63     """Support functions ACLs"""
64
65     def setUp(self):
66         """set up function"""
67         self.rmfiles = []
68         self.rmdirs = []
69
70     def tearDown(self):
71         """tear down function"""
72         for fname in self.rmfiles:
73             os.unlink(fname)
74         for dname in self.rmdirs:
75             os.rmdir(dname)
76
77     def _getfile(self):
78         """create a temp file"""
79         fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
80         self.rmfiles.append(fname)
81         return fh, fname
82
83     def _getdir(self):
84         """create a temp dir"""
85         dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
86         self.rmdirs.append(dname)
87         return dname
88
89     def _getsymlink(self):
90         """create a symlink"""
91         fh, fname = self._getfile()
92         os.close(fh)
93         os.unlink(fname)
94         os.symlink(fname + ".non-existent", fname)
95         return fname
96
97
98 class LoadTests(aclTest, unittest.TestCase):
99     """Load/create tests"""
100     def testFromFile(self):
101         """Test loading ACLs from a file"""
102         _, fname = self._getfile()
103         acl1 = posix1e.ACL(file=fname)
104         self.assertTrue(acl1.valid(), "ACL read from file should be valid")
105
106     def testFromDir(self):
107         """Test loading ACLs from a directory"""
108         dname = self._getdir()
109         acl1 = posix1e.ACL(file=dname)
110         acl2 = posix1e.ACL(filedef=dname)
111         self.assertTrue(acl1.valid(),
112                         "ACL read from directory should be valid")
113         # default ACLs might or might not be valid; missing ones are
114         # not valid, so we don't test acl2 for validity
115
116     def testFromFd(self):
117         """Test loading ACLs from a file descriptor"""
118         fd, _ = self._getfile()
119         acl1 = posix1e.ACL(fd=fd)
120         self.assertTrue(acl1.valid(), "ACL read from fd should be valid")
121
122     def testFromEmpty(self):
123         """Test creating an empty ACL"""
124         acl1 = posix1e.ACL()
125         self.assertFalse(acl1.valid(), "Empty ACL should not be valid")
126
127     def testFromText(self):
128         """Test creating an ACL from text"""
129         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
130         self.assertTrue(acl1.valid(),
131                         "ACL based on standard description should be valid")
132
133 class AclExtensions(aclTest, unittest.TestCase):
134     """ACL extensions checks"""
135
136     @has_ext(HAS_ACL_FROM_MODE)
137     def testFromMode(self):
138         """Test loading ACLs from an octal mode"""
139         acl1 = posix1e.ACL(mode=M0644)
140         self.assertTrue(acl1.valid(),
141                         "ACL created via octal mode shoule be valid")
142
143     @has_ext(HAS_ACL_CHECK)
144     def testAclCheck(self):
145         """Test the acl_check method"""
146         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
147         self.assertFalse(acl1.check(), "ACL is not valid")
148         acl2 = posix1e.ACL()
149         self.assertTrue(acl2.check(), "Empty ACL should not be valid")
150
151     @has_ext(HAS_EXTENDED_CHECK)
152     def testExtended(self):
153         """Test the acl_extended function"""
154         fd, fname = self._getfile()
155         basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
156         basic_acl.applyto(fd)
157         for item in fd, fname:
158             self.assertFalse(has_extended(item),
159                              "A simple ACL should not be reported as extended")
160         enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
161         self.assertTrue(enhanced_acl.valid(),
162                         "Failure to build an extended ACL")
163         enhanced_acl.applyto(fd)
164         for item in fd, fname:
165             self.assertTrue(has_extended(item),
166                             "An extended ACL should be reported as such")
167
168     @has_ext(HAS_EQUIV_MODE)
169     def testEquivMode(self):
170         """Test the equiv_mode function"""
171         if HAS_ACL_FROM_MODE:
172             for mode in M0644, M0755:
173                 acl = posix1e.ACL(mode=mode)
174                 self.assertEqual(acl.equiv_mode(), mode)
175         acl = posix1e.ACL(text="u::rw,g::r,o::r")
176         self.assertEqual(acl.equiv_mode(), M0644)
177         acl = posix1e.ACL(text="u::rx,g::-,o::-")
178         self.assertEqual(acl.equiv_mode(), M0500)
179
180
181 class WriteTests(aclTest, unittest.TestCase):
182     """Write tests"""
183
184     def testDeleteDefault(self):
185         """Test removing the default ACL"""
186         dname = self._getdir()
187         posix1e.delete_default(dname)
188
189     def testReapply(self):
190         """Test re-applying an ACL"""
191         fd, fname = self._getfile()
192         acl1 = posix1e.ACL(fd=fd)
193         acl1.applyto(fd)
194         acl1.applyto(fname)
195         dname = self._getdir()
196         acl2 = posix1e.ACL(file=fname)
197         acl2.applyto(dname)
198
199
200 class ModificationTests(aclTest, unittest.TestCase):
201     """ACL modification tests"""
202
203     def checkRef(self, obj):
204         """Checks if a given obj has a 'sane' refcount"""
205         if platform.python_implementation() == "PyPy":
206             return
207         ref_cnt = sys.getrefcount(obj)
208         # FIXME: hardcoded value for the max ref count... but I've
209         # seen it overflow on bad reference counting, so it's better
210         # to be safe
211         if ref_cnt < 2 or ref_cnt > 1024:
212             self.fail("Wrong reference count, expected 2-1024 and got %d" %
213                       ref_cnt)
214
215     def testStr(self):
216         """Test str() of an ACL."""
217         acl = posix1e.ACL()
218         str_acl = str(acl)
219         self.checkRef(str_acl)
220
221     @has_ext(HAS_ACL_ENTRY)
222     def testAppend(self):
223         """Test append a new Entry to the ACL"""
224         acl = posix1e.ACL()
225         e = acl.append()
226         e.tag_type = posix1e.ACL_OTHER
227         acl.calc_mask()
228         str_format = str(e)
229         self.checkRef(str_format)
230
231     @has_ext(HAS_ACL_ENTRY)
232     def testDelete(self):
233         """Test delete Entry from the ACL"""
234         acl = posix1e.ACL()
235         e = acl.append()
236         e.tag_type = posix1e.ACL_OTHER
237         acl.calc_mask()
238         acl.delete_entry(e)
239         acl.calc_mask()
240
241     @has_ext(HAS_ACL_ENTRY)
242     def testDoubleEntries(self):
243         """Test double entries"""
244         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
245         self.assertTrue(acl.valid(), "ACL is not valid")
246         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
247                          posix1e.ACL_OTHER):
248             e = acl.append()
249             e.tag_type = tag_type
250             e.permset.clear()
251             self.assertFalse(acl.valid(),
252                 "ACL containing duplicate entries"
253                 " should not be valid")
254             acl.delete_entry(e)
255
256     @has_ext(HAS_ACL_ENTRY)
257     def testMultipleGoodEntries(self):
258         """Test multiple valid entries"""
259         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
260         self.assertTrue(acl.valid(), "ACL is not valid")
261         for tag_type in (posix1e.ACL_USER,
262                          posix1e.ACL_GROUP):
263             for obj_id in range(5):
264                 e = acl.append()
265                 e.tag_type = tag_type
266                 e.qualifier = obj_id
267                 e.permset.clear()
268                 acl.calc_mask()
269                 self.assertTrue(acl.valid(),
270                     "ACL should be able to hold multiple"
271                     " user/group entries")
272
273     @has_ext(HAS_ACL_ENTRY)
274     def testMultipleBadEntries(self):
275         """Test multiple invalid entries"""
276         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
277         self.assertTrue(acl.valid(), "ACL built from standard description"
278                         " should be valid")
279         for tag_type in (posix1e.ACL_USER,
280                          posix1e.ACL_GROUP):
281             e1 = acl.append()
282             e1.tag_type = tag_type
283             e1.qualifier = 0
284             e1.permset.clear()
285             acl.calc_mask()
286             self.assertTrue(acl.valid(), "ACL should be able to add a"
287                 " user/group entry")
288             e2 = acl.append()
289             e2.tag_type = tag_type
290             e2.qualifier = 0
291             e2.permset.clear()
292             acl.calc_mask()
293             self.assertFalse(acl.valid(), "ACL should not validate when"
294                 " containing two duplicate entries")
295             acl.delete_entry(e1)
296             acl.delete_entry(e2)
297
298     @has_ext(HAS_ACL_ENTRY)
299     def testPermset(self):
300         """Test permissions"""
301         acl = posix1e.ACL()
302         e = acl.append()
303         ps = e.permset
304         ps.clear()
305         str_ps = str(ps)
306         self.checkRef(str_ps)
307         pmap = {
308             posix1e.ACL_READ: "read",
309             posix1e.ACL_WRITE: "write",
310             posix1e.ACL_EXECUTE: "execute",
311             }
312         for perm in pmap:
313             str_ps = str(ps)
314             self.checkRef(str_ps)
315             self.assertFalse(ps.test(perm), "Empty permission set should not"
316                 " have permission '%s'" % pmap[perm])
317             ps.add(perm)
318             self.assertTrue(ps.test(perm), "Permission '%s' should exist"
319                 " after addition" % pmap[perm])
320             str_ps = str(ps)
321             self.checkRef(str_ps)
322             ps.delete(perm)
323             self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
324                 " after deletion" % pmap[perm])
325
326
327     @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
328     def testQualifierValues(self):
329         """Tests qualifier correct store/retrieval"""
330         acl = posix1e.ACL()
331         e = acl.append()
332         # work around deprecation warnings
333         if hasattr(self, 'assertRegex'):
334             fn = self.assertRegex
335         else:
336             fn = self.assertRegexpMatches
337         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
338             qualifier = 1
339             e.tag_type = tag
340             while True:
341                 if tag == posix1e.ACL_USER:
342                     regex = re.compile("user with uid %d" % qualifier)
343                 else:
344                     regex = re.compile("group with gid %d" % qualifier)
345                 try:
346                     e.qualifier = qualifier
347                 except OverflowError:
348                     # reached overflow condition, break
349                     break
350                 self.assertEqual(e.qualifier, qualifier)
351                 fn(str(e), regex)
352                 qualifier *= 2
353
354     @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
355     def testQualifierOverflow(self):
356         """Tests qualifier overflow handling"""
357         acl = posix1e.ACL()
358         e = acl.append()
359         qualifier = sys.maxsize * 2
360         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
361             e.tag_type = tag
362             with self.assertRaises(OverflowError):
363                 e.qualifier = qualifier
364
365     @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
366     def testNegativeQualifier(self):
367         """Tests negative qualifier handling"""
368         # Note: this presumes that uid_t/gid_t in C are unsigned...
369         acl = posix1e.ACL()
370         e = acl.append()
371         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
372             e.tag_type = tag
373             for qualifier in [-10, -5, -1]:
374                 with self.assertRaises(OverflowError):
375                     e.qualifier = qualifier
376
377
378 if __name__ == "__main__":
379     unittest.main()