########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/Drivers/pAclObject.py,v 1.12 2005/03/28 11:31:13 mbrown Exp $
"""
ACL implementation

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""


from Ft.Server import FTSERVER_NAMESPACE
from Ft.Server.Common import AclConstants
from Ft.Server.Server import FtServerServerException, Error
from Ft.Server.Server.Drivers import PathImp
from Ft.Xml import EMPTY_NAMESPACE
from Ft.Xml import XPath


class AclObject:
    def __init__(self,driver, isAnonymous, aclIdents):
        self.__isAnonymous = isAnonymous
        self.__aclIdents = aclIdents
        self.__driver = driver
        self.__aclCache = {}
        return

    def clearCache(self):
        self.__aclCache = {}
        return

    def verifyFetch(self,path):
        """Verify that a resource can be fetched.
        1.  Verify that the user has traverse privileges to all of the parents
        2.  Verify that the user can read the last container"""
        self.__verifyAclSteps(path.absolutePaths, AclConstants.READ_ACCESS, 1)

    def verifyCreate(self, path):
        """Verify that a resource can be created.
        1.  Verify that the user has traverse privileges to all of the grand parents
        2.  Verify that the user can write the parent"""

        self.__verifyAclSteps(path.absolutePaths[:-1], AclConstants.WRITE_ACCESS, 1)

    def verifyWrite(self, path):
        """Verify that a resource can be written to.
        1.  Verify that the user has traverse privileges to all of the parents
        2.  Verify that the user can write the object"""

        self.__verifyAclSteps(path.absolutePaths, AclConstants.WRITE_ACCESS, 1)

    def verifyDelete(self, path):
        """Verify that a resource can be deleted.
        1.  Verify that the user has traverse privileges to all of the parents
        2.  Verify that the user can delete the object"""

        self.__verifyAclSteps(path.absolutePaths, AclConstants.DELETE_ACCESS, 1)

    def verifyChangePermissions(self, path):
        """Verify that there are change permission on the object"""
        self.__verifyAclSteps(path.absolutePaths, AclConstants.CHANGE_PERMISSIONS_ACCESS, 0)

    def verifyAcl(self, path, access, verifyTraverse):
        self.__verifyAclSteps(path.absolutePaths, access, verifyTraverse)

    def verifyAddMember(self, group, user):
        """Verify that the user can be added to the group"""
        self.verifyWrite(group)
        self.verifyFetch(user)

    def getAclIdentifiers(self):
        return self.__aclIdents

    def getAcl(self, path, access):
        acl,owner = self.__getResourceAcl(path)
        if not access:
            return acl
        if access in acl:
            return acl[access]
        if path.absolutePath == '/':
            return {}
        if path.displayPath == '/':
            #Need to by pass the doc root
            p = PathImp.CreateInitialPath('/', path._driver)
            path = p.normalize(path._documentRoot.absolutePath)

        return self.getAcl(path.getParentPath(), access)

    def __verifyAclSteps(self, steps, finalAccess, verifyTraverse):
        """Verify a specific access type on a resourc's md"""

        if steps[-1] in self.__aclCache:
            t = self.__aclCache[steps[-1]].get(finalAccess)
            if t == AclConstants.ALLOWED: return
            if t == AclConstants.DENIED:
                raise FtServerServerException(Error.PERMISSION_DENIED,
                                              level=finalAccess,
                                              path=steps[-1])
            #Sorry, not cached
        cur = None
        if verifyTraverse:
            for s in steps[:-1]:
                cur = self.__verifyAcl(s, AclConstants.EXECUTE_ACCESS, cur)

        self.__verifyAcl(steps[-1], finalAccess, cur)

    def __verifyAcl(self, path, access, parentAcl):
        """Verify one step in the ACL chain"""

        if AclConstants.SUPER_USER_GROUP_NAME in self.__aclIdents: return 1

        if path in self.__aclCache:
            t = self.__aclCache[path].get(access)
            if t == AclConstants.ALLOWED: return
            if t == AclConstants.DENIED:
                raise FtServerServerException(Error.PERMISSION_DENIED,
                                              level=access,
                                              path=path)

        acl,owner = self.__getResourceAcl(PathImp.QuickCreate(path))

        if parentAcl is not None:
            parentAcl.update(acl)
            acl = parentAcl

        if access not in acl and parentAcl is None:
            testPath = path
            while 1:
                if testPath == '/':
                    break

                #Calculate the parent path
                testPath = '/'.join(testPath.split('/')[:-1])
                if not testPath:
                    testPath = '/'
                parentAcl,o = self.__getResourceAcl(PathImp.QuickCreate(testPath))
                parentAcl.update(acl)
                acl = parentAcl
                if access in acl:
                    break

        cur = acl.get(access,{})

        #Resolve any owner ids
        if AclConstants.OWNER in cur and owner not in cur:
            cur[owner] = cur[AclConstants.OWNER]

        #See if we are explicitly disallowed
        for a in self.__aclIdents:
            if not cur.get(a, 1):
                self.__setCache(path, access, AclConstants.DENIED)
                raise FtServerServerException(Error.PERMISSION_DENIED,
                                              level=access,
                                              path=path)

        #See if we are allowed
        for a in self.__aclIdents:
            if cur.get(a, 0):
                self.__setCache(path, access, AclConstants.ALLOWED)
                return acl
        self.__setCache(path, access, AclConstants.DENIED)
        raise FtServerServerException(Error.PERMISSION_DENIED,
                                      level=access,
                                      path=path)


    def __setCache(self,path,access,allowed):

        if path not in self.__aclCache:
            self.__aclCache[path] = {}
        self.__aclCache[path][access] = allowed


    aclExpression = XPath.Compile('/ftss:MetaData/ftss:Acl/ftss:Access')
    ownerExpression = XPath.Compile('string(/ftss:MetaData/ftss:Owner)')
    def __getResourceAcl(self,path):

        con = self.__driver.getContext(path.normalize('.;metadata;no-traverse'))
        acl = {}
        for access in self.aclExpression.evaluate(con):
            typ = access.getAttributeNS(EMPTY_NAMESPACE,'type')
            allowed = access.getAttributeNS(EMPTY_NAMESPACE,'allowed')
            ident = access.getAttributeNS(EMPTY_NAMESPACE,'ident')
            if typ not in acl:
                acl[typ] = {}
            if allowed and int(allowed):
                acl[typ][ident] = AclConstants.ALLOWED
            else:
                acl[typ][ident] = AclConstants.DENIED

        owner = self.ownerExpression.evaluate(con)

        return acl,owner


