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