print """Hotfixing: AccessControl: Extending Local Roles,
           Lennart Regebro, 2001-10-04, version Easy Publisher 1.4.38"""

from AccessControl.Role import RoleManager
from AccessControl.User import BasicUser
import Globals
from Globals import DTMLFile
from App.Common import aq_base
from string import join
from AccessControl import ClassSecurityInfo
from urllib import quote

securityRoleManager = ClassSecurityInfo()

# Local roles support
# -------------------
#
# Local roles allow a user to be given extra roles in the context
# of a particular object (and its children). When a user is given
# extra roles in a particular object, an entry for that user is made
# in the __ac_local_roles__ dict containing the extra roles.

#__ac_local_roles__=None

#TODO: New methods need to have "Change permissions" premissions defined for them

__ac_local_roles_white__ = {}
__ac_local_roles_black__ = {}
RoleManager.__ac_local_roles_white__ = __ac_local_roles_white__
RoleManager.__ac_local_roles_black__ = __ac_local_roles_black__

manage_listLocalRoles=DTMLFile('dtml/listLocalRoles', globals(),
                               management_view='Security',
                               help_topic='Security_Local-Roles.stx',
                               help_product='OFSP')

RoleManager.manage_listLocalRoles = manage_listLocalRoles

manage_editLocalRoles=DTMLFile('dtml/editLocalRoles', globals(),
                               management_view='Security',
                               help_topic='Security_User-Local-Roles.stx',
                               help_product='OFSP')

RoleManager.manage_editLocalRoles = manage_editLocalRoles


def has_local_roles(self):
    dict=self.__ac_local_roles__()
    return len(dict)

RoleManager.has_local_roles = has_local_roles

def get_local_roles(self):
    dict=self.__ac_local_roles__()
    keys=dict.keys()
    keys.sort()
    info=[]
    for key in keys:
        value=tuple(dict[key])
        info.append((key, value))
    return tuple(info)

RoleManager.get_local_roles = get_local_roles

def get_valid_userids(self):
    item=self
    dict={}
    while 1:
        if hasattr(aq_base(item), 'acl_users') and \
           hasattr(item.acl_users, 'user_names'):
            for name in item.acl_users.user_names():
                dict[name]=1
        if not hasattr(item, 'aq_parent'):
            break
        item=item.aq_parent
    keys=dict.keys()
    keys.sort()
    return tuple(keys)

RoleManager.get_valid_userids = get_valid_userids

securityRoleManager.declarePrivate('possible_contributers')
RoleManager.possible_contributers = get_valid_userids

securityRoleManager.declarePrivate('get_valid_userids_for_role')
def get_valid_userids_for_role(self, role):
    item=self
    dict={}
    while 1:
        if hasattr(aq_base(item), 'acl_users') and \
           hasattr(item.acl_users, 'getUsers'):
            for user in item.acl_users.getUsers():
                if not dict.has_key(user.getId()) and user.has_role(role, object=self):
                    dict[user.getId()]=1
        if not hasattr(item, 'aq_parent'):
            break
        item=item.aq_parent
    keys=dict.keys()
    keys.sort()
    return tuple(keys)

RoleManager.get_valid_userids_for_role = get_valid_userids_for_role


def get_local_roles_for_userid(self, userid):
    #PATCH: temporary patch code
    if not callable(self.__ac_local_roles__): del self.__ac_local_roles__
    dict=self.__ac_local_roles__()
    return tuple(dict.get(userid, []))

RoleManager.get_local_roles_for_userid = get_local_roles_for_userid

def manage_addLocalRoles(self, userid, roles, REQUEST=None, RESPONSE=None):
    """Set local roles for a user."""
    if not roles:
        raise ValueError, 'One or more roles must be given!'
    dict=self.__ac_local_roles__()
    local_roles = list(dict.get(userid, []))
    for r in roles:
        if r not in local_roles:
            local_roles.append(r)
    dict[userid] = local_roles
    self.setLocalRoles(dict)
    if REQUEST and RESPONSE:
        message="Your changes have been saved."
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_addLocalRoles = manage_addLocalRoles

def manage_setLocalRoles(self, userid, roles, REQUEST=None, RESPONSE=None):
    """Set local roles for a user."""
    if not roles:
        raise ValueError, 'One or more roles must be given!'
    dict=self.__ac_local_roles__()
    dict[userid]=roles
    self.setLocalRoles(dict)
    if REQUEST and RESPONSE:
        message="Your changes have been saved."
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_setLocalRoles = manage_setLocalRoles

def manage_delLocalRoles(self, userids, REQUEST=None, RESPONSE=None):
    """Remove all local roles for a user."""
    dict=self.__ac_local_roles__()
    for userid in userids:
        if dict.has_key(userid):
            del dict[userid]
    self.setLocalRoles(dict)
    if REQUEST and RESPONSE:
        message="Users added as local authors."
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_delLocalRoles = manage_delLocalRoles


def __ac_local_roles__(self):
    local_roles = self.__ac_local_roles_white__ or {}
    local_roles = local_roles.copy()
    black = self.__ac_local_roles_black__ or {}
    blackusers = black.keys()
    for user, roles in local_roles.items():
        if user in blackusers:
            local_roles[user] = filter(lambda r, br=black[user]: r not in br, roles)
    return local_roles

RoleManager.__ac_local_roles__ = __ac_local_roles__


securityRoleManager.declarePrivate('setLocalRoles')
def setLocalRoles(self, local_roles_white):
    self.__ac_local_roles_white__=local_roles_white

RoleManager.setLocalRoles = setLocalRoles


securityRoleManager.declarePrivate('setLocalRolesBlack')
def setLocalRolesBlack(self, local_roles_black):
    self.__ac_local_roles_black__=local_roles_black

securityRoleManager.declarePrivate('getBlackRolesForUser')
def getBlackRolesForUser(self, user):
    return self.__ac_local_roles_black__.get(user, ())

securityRoleManager.declarePrivate('getBlackList')
def getBlackList(self, user):
    return self.__ac_local_roles_black__

RoleManager.setLocalRolesBlack = setLocalRolesBlack
RoleManager.getBlackRolesForUser = getBlackRolesForUser
RoleManager.getBlackList = getBlackList


# local_roles_with_hop
# {
#   'user1': {role1: hop0, role2: hop1, role3: hop0 },
#   'user2': {role1: hop1, role2: hop1, role3: hop1 },
# }

securityRoleManager.declarePrivate('get_local_roles_aq')
def get_local_roles_aq(self):
    hop=-1
    local_roles = {}
    black_roles = {}
    inner_obj = getattr(self, 'aq_inner', self)
    while 1:
        hop=hop + 1
        current_local_roles = getattr(inner_obj,'__ac_local_roles_white__',None)
        current_black_roles = getattr(inner_obj,'__ac_local_roles_black__',None)
        if current_black_roles is not None:
            for user, roles in current_black_roles.items():
                if not black_roles.has_key(user):
                    black_roles[user] = ()
                black_roles[user] = black_roles[user] + roles
        if current_local_roles is not None:
            for user, roles in current_local_roles.items():
                b_roles = black_roles.get(user, ())
                for role in roles:
                    if not local_roles.has_key(user):
                      local_roles[user] = {}
                    if (role not in local_roles[user].keys()) and (role not in b_roles):
                        local_roles[user][role] = hop
        inner = getattr(inner_obj, 'aq_inner', inner_obj)
        parent = getattr(inner, 'aq_parent', None)
        if parent is not None:
            inner_obj = parent
            continue
        if hasattr(inner_obj, 'im_self'):
            inner_obj=inner_obj.im_self
            inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
            continue
        break
    return local_roles


RoleManager.get_local_roles_aq = get_local_roles_aq

securityRoleManager.declarePrivate('filter_acquired_roles_for_userid')
def filter_acquired_roles_for_userid(self, userid, local_roles_with_hop=None):
    if local_roles_with_hop is None: local_roles_with_hop= {}
    if local_roles_with_hop.has_key(userid):
        return map(lambda (role, hop): role,filter(lambda (role, hop): hop!=0, local_roles_with_hop[userid].items()))
    return []

RoleManager.filter_acquired_roles_for_userid = filter_acquired_roles_for_userid

securityRoleManager.declarePrivate('getUserById')
def getUserById(self, userid):
    item=self
    while 1:
        if hasattr(aq_base(item), 'acl_users') and \
           hasattr(item.acl_users, 'user_names'):
            for name in item.acl_users.user_names():
                if userid == name:
                    #The User object needs to be in the context of the
                    #user folder that contains it.
                    return item.acl_users.getUser(userid).__of__(item.acl_users)
        if not hasattr(item, 'aq_parent'):
            break
        item=item.aq_parent
    return None

RoleManager.getUserById = getUserById


securityRoleManager.declareProtected('Change permissions', 'manage_addLocalRolesForUsers')
def manage_addLocalRolesForUsers(self, userids=None, roles=None, REQUEST=None, RESPONSE=None):
    """Add Local Users"""
    if roles is None: roles=[]
    if userids is None: userids=[]
    local_roles_white = self.__ac_local_roles_white__
    for user in userids:
        users_white_roles = list(local_roles_white[user])
        for role in roles:
            if role not in users_white_roles: users_white_roles.append(role)
        local_roles_white[user] = tuple(users_white_roles)
    self.setLocalRoles(local_roles_white)
    if REQUEST and RESPONSE:
        message="Lennart has been blacklisted as Author."
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_addLocalRolesForUsers = manage_addLocalRolesForUsers

securityRoleManager.declareProtected('Change permissions', 'manage_delLocalRolesForUsers')
def manage_delLocalRolesForUsers(self, userids=None, roles=None, REQUEST=None, RESPONSE=None):
    """Delete Local Users"""
    if roles is None: roles=[]
    if userids is None: userids=[]
    local_roles_white = self.__ac_local_roles_white__
    for user in userids:
        users_white_roles = list(local_roles_white[user])
        for role in roles:
            if role in users_white_roles: users_white_roles.remove(role)
        local_roles_white[user] = tuple(users_white_roles)
    self.setLocalRoles(local_roles_white)
    if REQUEST and RESPONSE:
        message="[%s] has been given the role(s): [%s]." % (join(userids, ', '),join(roles, ', '))
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_delLocalRolesForUsers = manage_delLocalRolesForUsers

securityRoleManager.declareProtected('Change permissions', 'manage_disableLocalRolesForUsers')
def manage_disableLocalRolesForUsers(self, userids=None, roles=None, REQUEST=None, RESPONSE=None):
    """Disable users from Local Role Access"""
    if roles is None: roles=[]
    if userids is None: userids=[]
    local_roles_black = self.__ac_local_roles_black__
    for user in userids:
        users_black_roles = list(local_roles_black.get(user,()))
        for role in roles:
            if role not in users_black_roles: users_black_roles.append(role)
        local_roles_black[user] = tuple(users_black_roles)
    self.setLocalRolesBlack(local_roles_black)
    if REQUEST and RESPONSE:
        message="[%s] has been removed as local role(s): [%s]."  % (join(userids, ', '),join(roles, ', '))
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_disableLocalRolesForUsers = manage_disableLocalRolesForUsers

securityRoleManager.declareProtected('Change permissions', 'manage_enableLocalRolesForUsers')
def manage_enableLocalRolesForUsers(self, userids=None, roles=None, REQUEST=None, RESPONSE=None):
    """Enable disabled users from Local Role Access"""
    if roles is None: roles=[]
    if userids is None: userids=[]
    local_roles_black = self.__ac_local_roles_black__
    for user in userids:
        users_black_roles = list(local_roles_black.get(user,()))
        for role in roles:
            if role in users_black_roles: users_black_roles.remove(role)
        local_roles_black[user] = tuple(users_black_roles)
    self.setLocalRolesBlack(local_roles_black)
    if REQUEST and RESPONSE:
        message="%s has been enabled as %s." % (join(userids,', '),join(roles,', '))
        RESPONSE.redirect('%s/manage_listLocalRoles?manage_tabs_message=%s' % (REQUEST.URL1, quote(message)))

RoleManager.manage_enableLocalRolesForUsers = manage_enableLocalRolesForUsers


RoleManager.security = securityRoleManager
Globals.InitializeClass(RoleManager)


# ==============================================================================
# PATCH OF AccessControl.User.py

securityBasicUser = ClassSecurityInfo()

def getRolesInContext(self, object):
    """
    Return the list of roles assigned to the user,
    including local roles assigned in context of
    the passed in object.
    """
    name=self.getUserName()
    roles=self.getRoles()
    black_roles = ()
    inner_obj = getattr(object, 'aq_inner', object)
    while 1:
        current_local_roles = getattr(inner_obj,'__ac_local_roles_white__',None)
        current_black_roles = getattr(inner_obj,'__ac_local_roles_black__',None)
        if current_black_roles is not None:
            black_roles = black_roles + current_black_roles.get(name, ())
        if current_local_roles is not None:
            for local_role in current_local_roles.get(name, ()):
                if (local_role not in roles) and (local_role not in black_roles):
                    roles = roles + (local_role,)
        inner = getattr(inner_obj, 'aq_inner', inner_obj)
        parent = getattr(inner, 'aq_parent', None)
        if parent is not None:
            inner_obj = parent
            continue
        if hasattr(inner_obj, 'im_self'):
            inner_obj=inner_obj.im_self
            inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
            continue
        break
    return roles

BasicUser.getRolesInContext = getRolesInContext

def allowed(self, object, object_roles=None):
    """Check whether the user has access to object. The user must
       have one of the roles in object_roles to allow access."""

    # Short-circuit the common case of anonymous access.
    if object_roles is None or 'Anonymous' in object_roles:
        return 1

    # Check for ancient role data up front, convert if found.
    # This should almost never happen, and should probably be
    # deprecated at some point.
    if 'Shared' in object_roles:
        object_roles = self._shared_roles(object)
        if object_roles is None or 'Anonymous' in object_roles:
            return 1

    # Check for a role match with the normal roles given to
    # the user, then with local roles only if necessary. We
    # want to avoid as much overhead as possible.
    user_roles = self.getRoles()
    for role in object_roles:
        if role in user_roles:
            if self._check_context(object):
                return 1
            return None

    # Still have not found a match, so check local roles. We do
    # this manually rather than call getRolesInContext so that
    # we can incur only the overhead required to find a match.

    inner_obj = getattr(object, 'aq_inner', object)
    name=self.getUserName()
    black_roles = ()
    while 1:
        current_local_roles = getattr(inner_obj,'__ac_local_roles_white__',None)
        current_black_roles = getattr(inner_obj,'__ac_local_roles_black__',None)
        if current_black_roles is not None:
            black_roles = black_roles + current_black_roles.get(name, ())
        if current_local_roles is not None:
            local_roles = current_local_roles.get(name, [])
            for role in object_roles:
                if role in local_roles and role not in black_roles:
                    if self._check_context(object):
                        return 1
                    return None
        inner = getattr(inner_obj, 'aq_inner', inner_obj)
        parent = getattr(inner, 'aq_parent', None)
        if parent is not None:
            inner_obj = parent
            continue
        if hasattr(inner_obj, 'im_self'):
            inner_obj=inner_obj.im_self
            inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
            continue
        break
    return None

BasicUser.allowed = allowed

BasicUser.security = securityBasicUser
Globals.InitializeClass(BasicUser)

