ACL implementation. Current features:
authorEdward Z. Yang <edwardzyang@thewritingpot.com>
Tue, 5 May 2009 06:52:19 +0000 (02:52 -0400)
committerEdward Z. Yang <edwardzyang@thewritingpot.com>
Tue, 5 May 2009 06:52:19 +0000 (02:52 -0400)
* afs.acl.ACL.retrieve(), equivalent to fs listacl
* afs.acl.getCallerAccess(), equivalent to fs getcalleraccess

Permissions are bitmasked, which can be masked against afs.acl.READ,
afs.acl.WRITE, etc.  Implementation is based off of Perl AFS module,
although there are some notable differences in API.

Signed-off-by: Edward Z. Yang <edwardzyang@thewritingpot.com>

afs/_acl.pyx [new file with mode: 0644]
afs/acl.py [new file with mode: 0644]
afs/afs.pxd
afs/afs.pyx
afs/tests/test_acl.py [new file with mode: 0644]
setup.py

diff --git a/afs/_acl.pyx b/afs/_acl.pyx
new file mode 100644 (file)
index 0000000..5f0360b
--- /dev/null
@@ -0,0 +1,46 @@
+from afs cimport *
+from afs import pyafs_error
+
+cdef import from "afs/prs_fs.h":
+    enum:
+        PRSFS_READ, PRSFS_WRITE, PRSFS_INSERT, PRSFS_LOOKUP,
+        PRSFS_DELETE, PRSFS_LOCK, PRSFS_ADMINISTER,
+        PRSFS_USR0, PRSFS_USR1, PRSFS_USR2, PRSFS_USR2, PRSFS_USR3,
+        PRSFS_USR4, PRSFS_USR5, PRSFS_USR6, PRSFS_USR7
+
+# This is defined in afs/afs.h, but I can't figure how to include the
+# header. Also, venus/fs.c redefines the struct, so why not!
+cdef struct vcxstat2:
+    afs_int32 callerAccess
+    afs_int32 cbExpires
+    afs_int32 anyAccess
+    char mvstat
+
+READ    = PRSFS_READ
+WRITE   = PRSFS_WRITE
+INSERT  = PRSFS_INSERT
+LOOKUP  = PRSFS_LOOKUP
+DELETE  = PRSFS_DELETE
+LOCK    = PRSFS_LOCK
+ADMINISTER = PRSFS_ADMINISTER
+USR0 = PRSFS_USR0
+USR1 = PRSFS_USR1
+USR2 = PRSFS_USR2
+USR3 = PRSFS_USR3
+USR4 = PRSFS_USR4
+USR5 = PRSFS_USR5
+USR6 = PRSFS_USR6
+USR7 = PRSFS_USR7
+
+DEF MAXSIZE = 2048
+
+def getAcl(char* dir, int follow=1):
+    cdef char space[MAXSIZE]
+    pioctl_read(dir, VIOCGETAL, space, MAXSIZE, follow)
+    ret = space # Python managed string
+    return ret
+
+def getCallerAccess(char *dir, int follow=1):
+    cdef vcxstat2 stat
+    pioctl_read(dir, VIOC_GETVCXSTATUS2, <void*>&stat, sizeof(vcxstat2), follow)
+    return stat.callerAccess
diff --git a/afs/acl.py b/afs/acl.py
new file mode 100644 (file)
index 0000000..b5da255
--- /dev/null
@@ -0,0 +1,79 @@
+import _acl
+from _acl import READ, WRITE, INSERT, LOOKUP, DELETE, LOCK, ADMINISTER, \
+    USR0, USR1, USR2, USR3, USR4, USR5, USR6, USR7
+from _acl import getCallerAccess
+
+_canonical = {
+    "read":     "rl",
+    "write":    "rwlidwk",
+    "all":      "rwlidwka",
+    "mail":     "lik",
+    "none":     "",
+}
+_char2bit = {
+    'r': READ,
+    'w': WRITE,
+    'i': INSERT,
+    'l': LOOKUP,
+    'd': DELETE,
+    'k': LOCK,
+    'a': ADMINISTER,
+    'A': USR0,
+    'B': USR1,
+    'C': USR2,
+    'D': USR3,
+    'E': USR4,
+    'F': USR5,
+    'G': USR6,
+    'H': USR7,
+}
+
+_bit2char = dict([(v,k) for k,v in _char2bit.items()])
+
+def crights(s):
+    """Canonicalizes string rights to bitmask"""
+    if s in _canonical: s = _canonical[s]
+    return _parseRights(s)
+
+class ACL(object):
+    def __init__(self, pos, neg):
+        """
+        ``pos``
+            Dictionary of usernames to positive ACL bitmasks
+        ``neg``
+            Dictionary of usernames to negative ACL bitmasks
+        """
+        self.pos = pos
+        self.neg = neg
+    @staticmethod
+    def retrieve(dir):
+        """Retrieve the ACL for an AFS directory"""
+        pos, neg = _parseAcl(_acl.getAcl(dir))
+        return ACL(pos, neg)
+
+def _parseRights(s):
+    """Parses a rwlid... rights tring to bitmask"""
+    r = 0
+    try:
+        for c in s:
+            r = r | _char2bit[c]
+    except KeyError:
+        raise ValueError
+    return r
+
+def _parseAcl(inp):
+    lines = inp.split("\n")
+    npos = int(lines[0].split(" ")[0])
+    pos = {}
+    neg = {}
+    for l in lines[2:]:
+        if l == "": continue
+        name, acl = l.split()
+        if npos:
+            npos -= 1
+            pos[name] = int(acl)
+        else:
+            # negative acl
+            neg[name] = int(acl)
+    return (pos, neg)
+
index d165921..02518e2 100644 (file)
@@ -146,3 +146,20 @@ cdef extern from "rx/rxkad.h":
     void initialize_RXK_error_table()
 cdef extern from "ubik.h":
     void initialize_U_error_table()
+
+cdef extern from "afs/vice.h":
+    struct ViceIoctl:
+        void *cin "in"
+        void *out
+        unsigned short out_size
+        unsigned short in_size
+
+cdef import from "afs/venus.h":
+    enum:
+        # PIOCTLS to Venus that we use
+        VIOCGETAL, VIOC_GETVCXSTATUS2
+
+# pioctl doesn't actually have a header, so we have to define it here
+cdef extern int pioctl(char *, afs_int32, ViceIoctl *, afs_int32)
+cdef int pioctl_read(char *, afs_int32, void *, unsigned short, afs_int32) except -1
+
index 8f51c16..77456b4 100644 (file)
@@ -4,8 +4,26 @@ General PyAFS utilities, such as error handling
 
 import sys
 
+# otherwise certain headers are unhappy
+cdef import from "netinet/in.h": pass
+cdef import from "afs/vice.h": pass
+
 cdef int _init = 0
 
+# pioctl convenience wrappers
+
+cdef extern int pioctl_read(char *dir, afs_int32 op, void *buffer, unsigned short size, afs_int32 follow) except -1:
+    cdef ViceIoctl blob
+    cdef afs_int32 code
+    blob.in_size  = 0
+    blob.out_size = size
+    blob.out = buffer
+    code = pioctl(dir, op, &blob, follow)
+    pyafs_error(code)
+    return code
+
+# Error handling
+
 class AFSException(Exception):
     def __init__(self, errno):
         self.errno = errno
diff --git a/afs/tests/test_acl.py b/afs/tests/test_acl.py
new file mode 100644 (file)
index 0000000..385e2ad
--- /dev/null
@@ -0,0 +1,18 @@
+import nose
+import afs.acl as acl
+
+def test_crights():
+    assert acl.crights('read') & acl.READ
+    assert acl.crights('read') & acl.LOOKUP
+    assert not acl.crights('read') & acl.WRITE
+
+def test_retrieve():
+    assert acl.ACL.retrieve('/afs/athena.mit.edu/contrib/bitbucket2').pos['system:anyuser'] & acl.WRITE
+    assert acl.ACL.retrieve('/afs/athena.mit.edu/user/t/a/tabbott').neg['yuranlu'] & acl.USR0
+
+def test_getCallerAccess():
+    assert acl.getCallerAccess('/afs/athena.mit.edu/contrib/bitbucket2') & acl.WRITE
+
+if __name__ == '__main__':
+    nose.main()
+
index e3a5133..912231c 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -40,6 +40,7 @@ setup(
     ext_modules=[
         PyAFSExtension("afs.afs"),
         PyAFSExtension("afs._pts"),
+        PyAFSExtension("afs._acl"),
         ],
     cmdclass= {"build_ext": build_ext}
 )