'''
Created on Oct 11, 2012

A dtel specification read from a dtel file

@author: carr
'''

from util.DirTree import DirTree
from util.DTELError import DTELError 
import AssignOption

class DTELSpecification(object):
 
    def __init__(self, types, domains, assignments, initialDomain) :
        '''
        
        :param types: The type delcarations
        :param domains: the domain declarations
        :param assignments: the type assignments
        :param initialDomain: the initial domain for the specification
        '''

        self.types = types
        self.domains = domains
        self.assignments = assignments
        self.initialDomain = initialDomain

        self.pathTree = DirTree() # file system directory tree build fromthe assign statements
        self.typeMap = dict() # a mapping from type name to type id
        self.domainMap = dict() # a mapping from domain name to domain id

        self.buildTypeTable()
        self.buildDomainTable()
        try :
            self.buildDirectoryTree()
        except DTELError as e:
            print("DTEL Spec Error:"+e.getMessage())
            exit(-1)
            
    def buildTypeTable(self) :
        '''
        Build a mapping from type name to a unique identifier
        '''
        
        i = 0
        for decl in self.types :
            
            for dtype in decl.getTypeList() :
  
                if dtype in self.typeMap :
                    print("Type '" + dtype + "' has multiple definitions.")
                    print("Aborting compilation.")
                    exit(-1)
                else :
                    self.typeMap[dtype] = i
                    i = i + 1

    def buildDomainTable(self) :
        '''
        Build a mapping from domain name to unique domain identifier
        '''
        
        i = 0
        for domain in self.domains :

            if domain.getName() in self.domainMap :
                print("Domain '" + domain.getName() + "' has multiple definitions.")
                print("Aborting compilation.")
                exit(-1)
            else :
                self.domainMap[domain.getName()] = i
                i = i + 1

    def buildDirectoryTree(self) :

        ''' 
        build directory tree  from the list of assign statements in the dtel file
        '''
        
        for dtype in self.assignments :
            for path in dtype.getPathList() :

                self.pathTree.addPath(path)
       
        ''' make the actual type assignments '''
        
        for dtype in self.assignments :
            
            if dtype.isRecursive() :
                flags = AssignOption.RECURSIVE
            else :
                flags = 0
            if dtype.isStatic() :
                flags |= AssignOption.STATIC
 
            if not dtype.getType() in self.typeMap :
                print("Use of invalid identifier '" + dtype.getType()
                        + "' with assign statement. Aborting compilation.")
                exit(-1)
           
            for path in dtype.getPathList() :
                self.pathTree.assignTypeToSubtree(path, self.typeMap[dtype.getType()], flags)

    
    def printDTELSpec(self) :
        '''
        Print a textual representation of the dtel specification
        '''

        self.printTypeDefinitions()

        print("\nNumber of Domains: "+len(self.domains))

        for domain in self.domains :
            print("\nDomain: "+self.domainMap[domain.getName()])
            print("\tName: "+domain.getName())

            self.printDomainEntryPoints(domain)

            self.printDomainPermissions(domain)

            self.printDomainTransitions(domain)

            if domain.isSetAuth() :
                print("\tsetauth")

        print("Initial domain: "+self.initialDomain)
        
        self.pathTree.printTree()

    
    def printTypeDefinitions(self) :
        ''' emit number of types '''

        keys = self.typeMap.keys()

        for key in keys :
            print("Type "+self.typeMap[key]+": "+key)
 
    def printDomainEntryPoints(self, domain) :

        entries = domain.getEntryPoints()

        for entry in entries :
            print("\t\tEntry point: "+entry.getEntryPoint())
    
    def printntDomainPermissions(self, domain) :

        perms = domain.getPermissions()

        for permission in perms :
            print("\t\tPermissions: "+permission.getPermissionRep())

            types = permission.getTypes()

            for tName in types :
                print("\t\t\tType: "+self.typeMap[tName])

    def printDomainTransitions(self, domain) :

        transitions = domain.getTransitions()

        for transition in transitions :
            print("\t\tTransition: "+transition.getTransTypeRep())


        domains = transition.getDomains()
        for dName in domains :
            print("\t\t\t Domain: "+self.domainMap[dName])

    '''
    * Semantic error checker added by J.W.
    * Does semantic checking on the DTE specification and exits with
    * a list of error messages if any errors are found.
    '''
                
    def dtelErrorCheck(self) :

        errorMessages = []
        errorsFound = False
        
        ''' Don't allow a domain & type of the same name '''
            
        keys = self.domainMap.keys()
        for key in keys :
            if key in self.typeMap :
                errorMessages.append("Type and domain lists both contain identifier '" + key + "'")
                errorsFound = True
 
        hasGenericType = False
        for assign in self.assignments :
            for path in assign.getPathList() :
                if path == '/' :
                    hasGenericType = True
                         
                ''' No duplicate type assignments allowed '''
                        
                for assign2 in self.assignments[self.assignments.index(assign)+1:len(self.assignments)] :
                    if assign2.getType() == assign.getType() :
                        if path  in assign2.getPathList() :
                            errorMessages.append("Path '" + path + "' assigned to multiple types")
                            errorsFound = True
        
        ''' A generic type is mandatory '''
        if hasGenericType == False :
            errorMessages.append("There is no generic type defined.")
            errorsFound = True
            

        ''' Initial domain must be a domain '''
        if not self.initialDomain  in self.domainMap :
            errorMessages.append("initial_domain set to '" + self.initialDomain + "' which is not defined as a domain")
            errorsFound = True
    

        for domain in self.domains :
            ''' Make sure permissions are only applied to types '''
            for permission in domain.getPermissions() :
                for tName in permission.getTypes() :
                    if not tName in self.typeMap :
                        errorMessages.append("Permissions tried to reference undefined type '" + tName + "'")
                        errorsFound = True
            '''Make sure we don't try to exec or auto on a non-domain target'''
            for transition in domain.getTransitions() :
                for dName in transition.getDomains() :
                    if not dName in self.domainMap :
                        errorMessages.append("Attempted exec or auto transition to '" + dName + "' which is not defined as a domain")
                        errorsFound = True
 
        if errorsFound :
            print("The following errors were found:")
            for err in errorMessages :
                print(err)
            exit(-1)
 

    '''
    * JSM Spec emitter added by J.W.
    * Converts the specified DTE spec into a Java Security Manager subclass
    * It emits raw Java source which will need to be compiled afterwards
    '''
    def emitJSM(self) :

        print("import java.util.*")
        print("import java.io.*")

        print("class DTESecurityManager extends SecurityManager {")

        ''' JSM's internal representation details of the DTE spec '''

        print("\tclass Permission {")
        print("\t\tpublic ArrayList<String> types;")
        print("\t\tpublic boolean read = false;")
        print("\t\tpublic boolean write = false;")
        print("\t\tpublic boolean exec = false;")
        print("\t\tpublic boolean dir = false;")
        print("\t\tpublic boolean create = false;")
        print("\t}")

        print("\tclass Transition {")
        print("\t\tpublic boolean auto = false;")
        print("\t\tpublic boolean exec = false;")
        print("\t\tpublic ArrayList<String> domains;")
        print("\t}")

        print("\tclass Domain {")
        print("\t\tpublic String name;")
        print("\t\tpublic ArrayList<String> entryPoints;")
        print("\t\tpublic ArrayList<Permission> permissions;")
        print("\t\tpublic ArrayList<Transition> transitions;")
        print("\t}")

        print("\tclass Type {")
        print("\t\tpublic String name;")
        print("\t\tpublic ArrayList<TypeAssignment> assignments;")
        print("\t}")

        print("\tclass TypeAssignment {")
        print("\t\tpublic boolean recursive;")
        print("\t\tpublic boolean staticOpt;")
        print("\t\tpublic ArrayList<String> paths;")
        print("\t}")

        print("\tprivate String currentDirectory;")
        print("\tprivate ArrayList<Domain> domains;")
        print("\tprivate ArrayList<Type> types;")
        print("\tprivate int currentDomain;")

        ''' JSM constructor '''
        print("\tDTESecurityManager() {")
        print("\t\tsuper();")
        print("\t\tcurrentDirectory = convertPath(System.getProperty(\"user.dir\"));") 
        print("\t\tdomains = new ArrayList<Domain>();")
        print("\t\ttypes = new ArrayList<Type>();")

        ''' Hard - code domain info into the class'''
        i = 0
        for domain in self.domains :
            print("\t\tDomain domain" +str(i) + " = new Domain();")
            print("\t\tdomain" + str(i) + ".entryPoints = new ArrayList<String>();")
            print("\t\tdomain" + str(i) + ".permissions = new ArrayList<Permission>();")
            print("\t\tdomain" + str(i) + ".transitions = new ArrayList<Transition>();")
            print("\t\tdomain" + str(i) + ".name = \"" + domain.getName() + "\";")
            for entry in domain.getEntryPoints() :
                print("\t\tdomain" + str(i) + ".entryPoints.add(\"" + entry.getEntryPoint() + "\");")
            
            j = 0
            for perm in domain.getPermissions() :
                print("\t\tPermission permission" +str(i) + "_" + str(j) + " = new Permission();")
                print("\t\tpermission" + str(i) + "_" + str(j) + ".types = new ArrayList<String>();")
                for tName in perm.getTypes() :
                    print("\t\tpermission" + str(i) + "_" + str(j) + ".types.add(\"" + tName + "\");")
                        
                permRep = perm.getPermissionRepList()
                if (permRep[0]) : print("\t\tpermission" + str(i) + "_" + str(j) + ".read = true;")
                if (permRep[1]) : print("\t\tpermission" + str(i) + "_" + str(j) + ".write = true;")
                if (permRep[2]) : print("\t\tpermission" + str(i) + "_" + str(j) + ".exec = true;")
                if (permRep[3]) : print("\t\tpermission" + str(i) + "_" + str(j) + ".dir = true;")
                if (permRep[4]) : print("\t\tpermission" + str(i) + "_" + str(j) + ".create = true;")
                print("\t\tdomain" + str(i) + ".permissions.add(permission" + str(i) + "_" + str(j) + ");")
                j  = j + 1
                
            k = 0
            for trans in domain.getTransitions() :
                print("\t\tTransition transition" + str(i) + "_" + str(k) + " = new Transition();")
                print("\t\ttransition" +str(i)  + "_" + str(k) + ".domains = new ArrayList<String>();")
                for transDom in trans.getDomains() :
                    print("\t\ttransition" + str(i) + "_" + str(k) + ".domains.add(\"" + transDom + "\");")
                
                if trans.getTransTypeRep() == 1 : print("\t\ttransition" + str(i) + "_" + str(k) + ".exec = true;")
                else : print("\t\ttransition" + str(i) + "_" + str(k) + ".auto = true;")
                        
                print("\t\tdomain" + str(i) + ".transitions.add(transition" + str(i) + "_" + str(k) + ");")
                k = k + 1
                    
            print("\t\tdomains.add(domain" + str(i) + ");")
            i = i + 1

        print("")

        '''Hard - code type info into the class'''
         
        typesToAdd = []       
        i = 0
        for decl in self.types :
            for typeName in decl.getTypeList() :
                
                defined = False
                j = 0
                for typAssn in self.assignments :
                    if typAssn.getType() == typeName :
                        if not defined :
                            print("\t\tType type" + str(i) + " = new Type();")
                            print("\t\ttype" + str(i) + ".assignments = new ArrayList<TypeAssignment>();")
                            print("\t\ttype" + str(i) + ".name = \"" + typeName + "\";")
                            typesToAdd.append("type" + str(i))
                            defined = True
                                
                        print("\t\tTypeAssignment assignment" + str(i) + "_" + str(j)+ "= new TypeAssignment();")
                        if typAssn.isRecursive() :
                            print("\t\tassignment" + str(i)+ "_" + str(j)+ ".recursive = true;")
                        else :
                            print("\t\tassignment" + str(i)+ "_" + str(j)+ ".recursive = false;")
                           
                        if typAssn.isStatic() :
                            print("\t\tassignment" + str(i)+ "_" + str(j)+ ".staticOpt = true;")
                        else :
                            print("\t\tassignment" + str(i)+ "_" + str(j)+ ".staticOpt = false;")
                                
                        print("\t\tassignment" + str(i)+ "_" + str(j)+ ".paths = new ArrayList<String>();")
                        for path in typAssn.getPathList() :
                            print("\t\tassignment" + str(i)+ "_" + str(j)+ ".paths.add(\"" + path + "\");")
                            
                        print("\t\ttype" + str(i)+ ".assignments.add(" + "assignment" + str(i)+ "_" + str(j)+ ");")
                        j = j + 1
                i = i + 1
   
        for t in typesToAdd : print("\t\ttypes.add(" + t + ");")

        ''' Assign initial domain '''
        print("\t\tString initialDomainName = \"" + self.initialDomain + "\";")
        print("\t\tfor (int i = 0 i < domains.size()i ++) {")
        print("\t\t\tif (domains.get(i).name.equals(initialDomainName)) {")
        print("\t\t\t\tcurrentDomain = i;")
        print("\t\t\t\tbreak;")
        print("\t\t\t}")
        print("\t\t}")

        print("\t}")
        ''' End JSM constructor '''

        print("")

        ''' Create a helper method to convert Windows paths into Unix paths (useful if running on Windows) '''
        print("\tString convertPath(String path) {")
        print("\t\tString rPath = path.replace('" + "\\" + "\\" + "', '/');")
        print("\t\tif (rPath.substring(1, 3).equals(\":/\")) rPath = rPath.substring(2);")
        print("\t\treturn rPath;")
        print("\t}")

        ''' Create a helper method to combine two paths into one (moving up the "left" path as necessary when "../" prefaces the right path) '''
        print("\tString combinePaths(String left, String right) {")
        print("if (right.startsWith(\"/\")) return right;")
        print("\t\tboolean fromRoot = left.startsWith(\"/\");")
        print("\t\tString[] leftSplit = left.split(\"/\");")
        print("\t\tint directoriesUp = 0;")
        print("\t\twhile (right.startsWith(\"../\")) {")
        print("\t\t\tdirectoriesUp++;")
        print("\t\t\tright = right.substring(3);")
        print("\t\t}")
        print("\t\tint implode = leftSplit.length - directoriesUp;")
        print("\t\tString leftRebuilt = (fromRoot ? \"/\" : \"\");")
        print("\t\tfor (int i = 0 i < implode i++) leftRebuilt += (leftSplit[i] + (leftSplit[i].equals(\"\") ? \"\" : \"/\"));")
        print("\t\tif (right.startsWith(\"/\")) right = right.substring(1);")
        print("\t\treturn leftRebuilt + right;")
        print("\t}")

        ''' Create a helper method to return a list of types that contain a given path '''
        print("\tArrayList<String> getTypes(String path) {")
        print("\t\tArrayList<String> paths = new ArrayList<String>();")
        print("\t\tfor (int i = 0 i < types.size() i++) {")
        print("\t\t\tboolean foundMatch = false;")
        print("\t\t\tfor (int j = 0 j < types.get(i).assignments.size() j++) {")
        print("\t\t\t\tfor (int k = 0 k < types.get(i).assignments.get(j).paths.size() k++) {")
        print("\t\t\t\t\tif (path.startsWith(types.get(i).assignments.get(j).paths.get(k))) {")
        print("\t\t\t\t\t\tif (!paths.contains(types.get(i).name)) paths.add(types.get(i).name);")
        print("\t\t\t\t\t\tfoundMatch = true;")
        print("\t\t\t\t\t\tbreak;")
        print("\t\t\t\t\t}")
        print("\t\t\t\t}")
        print("\t\t\t\tif (foundMatch) break;")
        print("\t\t\t}")
        print("\t\t}")
        print("\t\treturn paths;")
        print("\t}")

        ''' Create a helper method that returns whether the current domain has permission to do "permission" to the supplied type '''
        print("\tboolean checkPermission(String type, int permission) {")
        print("\t\tfor (Permission p : domains.get(currentDomain).permissions) {")
        print("\t\t\tfor (String t : p.types) {")
        print("\t\t\t\tif (t.equals(type)) {")
        print("\t\t\t\t\tswitch (permission) {")
        print("\t\t\t\t\t\tcase 0: if (p.create) return true break;")
        print("\t\t\t\t\t\tcase 1: if (p.read)   return true break;")
        print("\t\t\t\t\t\tcase 2: if (p.write)  return true break;")
        print("\t\t\t\t\t\tcase 3: if (p.exec)   return true break;")
        print("\t\t\t\t\t\tcase 4: if (p.dir)    return true break;")
        print("\t\t\t\t\t}")
        print("\t\t\t\t}")
        print("\t\t\t}")
        print("\t\t}")
        print("\t\treturn false;")
        print("\t}")

        ''' Helper method for basic filesystem check pattern '''
        print("\tboolean filesystemCheck(String filename, int permission) {")
        print("\t\tString fullpath = combinePaths(currentDirectory, convertPath(filename));")
        print("\t\tArrayList<String> typesToCheck = getTypes(fullpath);")
        print("\t\tif (typesToCheck == null) throw new SecurityException(\"Invalid path format.\");")
        print("\t\tfor (String t : typesToCheck) {")
        print("\t\t\tif (checkPermission(t, permission)) return true;")
        print("\t\t}")
        print("\t\treturn false;")
        print("\t}")

        print("")

        ''' override checkDelete(String filename) '''
        print("\tpublic void checkDelete(String filename) {")
        print("\t\tif (filesystemCheck(filename, 2)) return;")
        print("\t\tthrow new SecurityException(\"Current domain does not have write permission to \" + filename);")
        print("\t}")

        ''' override checkRead(String filename) '''
        print("\tpublic void checkRead(String filename) {")
        print("\t\tif (filesystemCheck(filename, 1)) return;")
        print("\t\tthrow new SecurityException(\"Current domain does not have read permission to \" + filename);")
        print("\t}")

        ''' override checkRead(String filename, Object executionContext) - -executionContext is ignored we don't care about it '''
        print("\tpublic void checkRead(String filename, Object executionContext) {")
        print("\t\tif (filesystemCheck(filename, 1)) return;")
        print("\t\tthrow new SecurityException(\"Current domain does not have read permission to \" + filename);")
        print("\t}")

        ''' override checkWrite(String filename) '''
        print("\tpublic void checkWrite(String filename) {")
        print("\t\tif (filesystemCheck(filename, 2)) return;")
        print("\t\tthrow new SecurityException(\"Current domain does not have write permission to \" + filename);")
        print("\t}")

        print("")

        ''' close class '''
        print("}")

