From: Evan Broder Date: Thu, 9 Jul 2009 02:32:01 +0000 (-0700) Subject: Merge branch 'acl' X-Git-Tag: 0.1.0~23 X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/python-afs.git/commitdiff_plain/a92dc43b602d9b3e37d96c11e9da9e543a330818?hp=dca079a5eac10b24c353149619b73413486f68b9 Merge branch 'acl' --- diff --git a/afs/_acl.pyx b/afs/_acl.pyx new file mode 100644 index 0000000..ef65388 --- /dev/null +++ b/afs/_acl.pyx @@ -0,0 +1,45 @@ +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) + return space + +def getCallerAccess(char *dir, int follow=1): + cdef vcxstat2 stat + pioctl_read(dir, VIOC_GETVCXSTATUS2, &stat, sizeof(vcxstat2), follow) + return stat.callerAccess diff --git a/afs/acl.py b/afs/acl.py new file mode 100644 index 0000000..da0821f --- /dev/null +++ b/afs/acl.py @@ -0,0 +1,88 @@ +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": "", +} + +_charBitAssoc = [ + ('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), +] + +_char2bit = dict(_charBitAssoc) + + +def readRights(s): + """Canonicalizes string rights to bitmask""" + if s in _canonical: s = _canonical[s] + return _parseRights(s) + +def showRights(r): + """Takes a bitmask and returns a rwlidka string""" + s = "" + for char,mask in _charBitAssoc: + if r & mask == mask: s += char + return s + +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) + +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, follow=1): + """Retrieve the ACL for an AFS directory""" + pos, neg = _parseAcl(_acl.getAcl(dir, follow)) + return ACL(pos, neg) + diff --git a/afs/afs.pxd b/afs/afs.pxd index d165921..02518e2 100644 --- a/afs/afs.pxd +++ b/afs/afs.pxd @@ -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 + diff --git a/afs/afs.pyx b/afs/afs.pyx index 8f51c16..29a2442 100644 --- a/afs/afs.pyx +++ b/afs/afs.pyx @@ -4,8 +4,30 @@ 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) + # This might work with the rest of OpenAFS, but I'm not convinced + # the rest of it is consistent + if code == -1: + raise OSError(errno, strerror(errno)) + 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 index 0000000..c6a7338 --- /dev/null +++ b/afs/tests/test_acl.py @@ -0,0 +1,21 @@ +import nose +import afs.acl as acl + +def test_showRights(): + assert acl.showRights(acl.READ | acl.WRITE) == "rw" + +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() + diff --git a/setup.py b/setup.py index e3a5133..912231c 100755 --- 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} )