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