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