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