"""
file:   rbacpolicy.py
author: tllake
email:  thomas.l.lake@wmich.edu
description:
        Functions for converting a rbac policy into a 
        set of datastructures representing the policy.
"""
from collections import namedtuple
from rbacparse import rbacparse
import re

Permset = namedtuple('Permset', ('perms', 'recursive'))
RoleNode = namedtuple('RoleNode', ('parents', 'children'))

def makepermset(iterable=None, recursive=False):
    perms = set() if iterable is None else set(iterable)
    return Permset(perms, recursive)

def makerolenode(parents=None, children=None):
    parentset = set() if parents is None else set(parents)
    childset = set() if children is None else set(children)
    return RoleNode(parentset, childset)

def rbacpolicy(source, verbose=False):
    """Initialize an RBAC policy representation.
    Args:
        source (file or filename): The RBAC policy file
        verbose (bool): be verbose.
    
    Returns:  
        user_role (dict): The user-role matrix. 
            user_role['user'] gives the set of roles associated with user 'user'.
        
        role_res (dict): The role-resource matrix.
            role_res['role']['res'] contains a namedtuple with fields 
            perms (set) and recursive (bool). perms is the set of permissions
            that 'role' has for 'res'. The recursive flag determines if these
            permissions should be applied recursively.
        
        role_hier (dict): The role hierarchy. 
            role_hier['role'].parents is the set of roles which 'role' 
            dominates/inherits from. Likewise role_hier['role'].children 
            is the set of roles which are dominated by/inherit from 'role'.
    """
    user_role = {}      # user_role['user'] := set(role_1,...,role_k)
    role_res = {}       # role_res['role']['res'] := (set(perm_1,...,perm_k), recursive_flag)
    role_hier = {}      # role_hier['role'] := (set(parent_1,...,parent_k), set(child_1,...,child_k))

    try:
        text = source.read()
    except AttributeError:
        with open(source, 'r') as f:
            text = f.read()
            
    stmts = rbacparse(text)
    for stmt in stmts:
        stmttype = stmt[0]
        if stmttype == 'user:':
            roles, users = stmt[1:]
#             p = re.compile('None', re.IGNORECASE)
#             if p.match(roles[0]):
#                 roles = ['None']
            for user in users:
                if verbose:
                    print 'adding roles: {0} to user: {1}'.format(roles, user)
                try:
                    user_role[user].update(roles)
                except KeyError:
                    user_role[user] = set(roles)
        elif stmttype == 'free_user:':
            users = stmt[1]
            for user in users:
#                 try:
#                     user_role[user].update(roles)
#                 except KeyError:
                user_role[user] = set() 
        elif stmttype == 'object:':
            '''mandy'''
            if isinstance(stmt[1], str):
                resource = stmt[1]
                role = 'None'
                permset = makepermset(set(), recursive=False)
                role_res[role] = {resource: permset}
                '''mandy'''
            else:
                roles, perms, resource = stmt[1], stmt[2], stmt[-1]
                if resource[-1]=='/' and resource!='/':
                    resource = resource[:-1]
                if len(stmt) == 5:
                    permset = makepermset(perms, recursive=True)
                else:
                    permset = makepermset(perms, recursive=False)
                for role in roles:
                    if verbose:
                        print 'adding perms: {0} to role: {1}, resource: {2}'.format(permset, role, resource)
                    try:
                        resourcedict = role_res[role]
                        resourcedict[resource] = permset
                    except KeyError:
                        # role has no resources
                        role_res[role] = {resource: permset}
        elif stmttype == 'inheritance:':
            #parent, children = stmt[1], stmt[3]
            '''mandy'''
            if len(stmt) == 4:
                parent, children = stmt[1], stmt[3]
            else:
                parent = stmt[1]
                children = []
            '''mandy'''
            if verbose:
                print 'adding {0} as children of {1}'.format(children, parent)
            try:
                parentnode = role_hier[parent]
                parentnode.children.update(children)
            except KeyError:
                role_hier[parent] = makerolenode(children=children)
            for child in children:
                if verbose:
                    print 'adding {0} as parent of {1}'.format(parent, child)
                try:
                    childnode = role_hier[child]
                    childnode.parents.add(parent)
                except KeyError:
                    role_hier[child] = makerolenode(parents=(parent,))
        else:
            raise ValueError('unknown statement type: {0}'.format(stmttype))
    
    return user_role, role_res, role_hier

if __name__ == '__main__':
    import sys
    if len(sys.argv) < 2:
        source = sys.stdin
    else:
        source = sys.argv[1]
    user_role, role_res, role_hier = rbacpolicy(source)
    
    print '== Users and Roles =='
    for user, roles in user_role.items():
        print 'user = {0}'.format(user)
        print '    roles = {0}'.format(', '.join(roles))
    
    print '== Roles and Resources =='
    for role, resdict in role_res.items():
        print 'roles = {0}'.format(role)
        for res, permset in resdict.items():
            print '    resource = {0}'.format(res)
            print '        perms = {0}'.format(', '.join(permset.perms))
            print '        recursive = {0}'.format(permset.recursive)

    print '== Role Hierarchy =='
    for role, rolenode in role_hier.items():
        print 'role = {0}'.format(role)
        print '    parents = {0}'.format(', '.join(rolenode.parents))
        print '    children = {0}'.format(', '.join(rolenode.children))

