# pas alterations and monkies
from zope.event import notify
 
from Products.CMFCore.utils import getToolByName
 
from AccessControl import Unauthorized, getSecurityManager
from AccessControl.Permissions import manage_users as ManageUsers
from AccessControl.Permissions import manage_properties, change_permissions
from AccessControl.PermissionRole import PermissionRole
 
from Products.PluggableAuthService.PluggableAuthService import \
     PluggableAuthService, _SWALLOWABLE_PLUGIN_EXCEPTIONS
from Products.PluggableAuthService.interfaces.plugins \
    import IRoleAssignerPlugin
from Products.PluggableAuthService.interfaces.plugins \
    import IUserEnumerationPlugin
from Products.PluggableAuthService.interfaces.plugins \
    import IGroupEnumerationPlugin
from Products.PluggableAuthService.events import PrincipalDeleted
 
from Products.PlonePAS.interfaces.plugins \
    import IUserManagement, ILocalRolesPlugin
from Products.PlonePAS.interfaces.group import IGroupIntrospection
from Products.PlonePAS.interfaces.plugins import IUserIntrospection
from AccessControl.requestmethod import postonly
 
# Register the PAS acl_users as a utility
from Products.CMFCore.utils import registerToolInterface
from Products.PluggableAuthService.interfaces.authservice \
    import IPluggableAuthService
registerToolInterface('acl_users', IPluggableAuthService)
 
 
#################################
# pas folder monkies - standard zope user folder api
 
_old_doAddUser = PluggableAuthService._doAddUser
 
 
def _doAddUser(self, login, password, roles, domains, groups=None, **kw):
    """Masking of PAS._doAddUser to add groups param."""
    retval = _old_doAddUser(self, login, password, roles, domains)
    if groups is not None:
        self.userSetGroups(login, groups)
    return retval
 
PluggableAuthService._doAddUser = _doAddUser
 
 
def _doDelUsers(self, names, REQUEST=None):
    """
    Delete users given by a list of user ids.
    Has no return value, like the original (GRUF).
    """
    for name in names:
        self._doDelUser(name)
 
PluggableAuthService._doDelUsers = _doDelUsers
 
 
def _doDelUser(self, id):
    """
    Given a user id, hand off to a deleter plugin if available.
    """
    plugins = self._getOb('plugins')
    userdeleters = plugins.listPlugins(IUserManagement)
 
    if not userdeleters:
        raise NotImplementedError("There is no plugin that can "
                                   " delete users.")
 
    for userdeleter_id, userdeleter in userdeleters:
        try:
            userdeleter.doDeleteUser(id)
        except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
            pass
        else:
            notify(PrincipalDeleted(id))
 
 
PluggableAuthService._doDelUser = _doDelUser
 
PluggableAuthService.userFolderDelUsers = \
    postonly(PluggableAuthService._doDelUsers)
PluggableAuthService.userFolderDelUsers__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def _doChangeUser(self, principal_id, password, roles, domains=(), groups=None,
                  REQUEST=None, **kw):
    """
    Given a principal id, change its password, roles, domains, if
    respective plugins for such exist.
 
    XXX domains are currently ignored.
    """
    # Might be called with 'None' as password from the Plone UI, in
    # prefs_users_overview when resetPassword is not set.
    if password is not None:
        self.userSetPassword(principal_id, password)
 
    plugins = self._getOb('plugins')
    rmanagers = plugins.listPlugins(IRoleAssignerPlugin)
 
    if not (rmanagers):
        raise NotImplementedError("There is no plugin that can modify roles")
 
    for rid, rmanager in rmanagers:
        rmanager.assignRolesToPrincipal(roles, principal_id)
 
    if groups is not None:
        self.userSetGroups(principal_id, groups)
 
    return True
 
PluggableAuthService._doChangeUser = _doChangeUser
 
PluggableAuthService.userFolderEditUser = \
    postonly(PluggableAuthService._doChangeUser)
PluggableAuthService.userFolderEditUser__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def userFolderAddUser(self, login, password, roles, domains,
                      groups=None, REQUEST=None, **kw):
    self._doAddUser(login, password, roles, domains, **kw)
    if groups is not None:
        self.userSetGroups(login, groups)
 
PluggableAuthService.userFolderAddUser = postonly(userFolderAddUser)
PluggableAuthService.userFolderAddUser__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def _doAddGroup(self, id, roles, groups=None, **kw):
    gtool = getToolByName(self, 'portal_groups')
    return gtool.addGroup(id, roles, groups, **kw)
 
PluggableAuthService._doAddGroup = _doAddGroup
 
 
# for prefs_group_manage compatibility. really should be using tool.
def _doDelGroups(self, names, REQUEST=None):
    gtool = getToolByName(self, 'portal_groups')
    for group_id in names:
        gtool.removeGroup(group_id)
 
PluggableAuthService._doDelGroups = _doDelGroups
 
PluggableAuthService.userFolderDelGroups = \
    postonly(PluggableAuthService._doDelGroups)
PluggableAuthService.userFolderDelGroups__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def _doChangeGroup(self, principal_id, roles, groups=None, REQUEST=None, **kw):
    """
    Given a group's id, change its roles, domains, if respective
    plugins for such exist. Domains are currently ignored.
 
    See also _doChangeUser
    """
    gtool = getToolByName(self, 'portal_groups')
    gtool.editGroup(principal_id, roles, groups, **kw)
    return True
 
PluggableAuthService._doChangeGroup = _doChangeGroup
 
 
def _updateGroup(self, principal_id, roles=None, groups=None, **kw):
    """
    Given a group's id, change its roles, groups, if respective
    plugins for such exist. Domains are ignored.
 
    This is not an alias to _doChangeGroup because its params are different
    (slightly).
    """
    return self._doChangeGroup(principal_id, roles, groups, **kw)
 
PluggableAuthService._updateGroup = _updateGroup
 
PluggableAuthService.userFolderEditGroup = \
    postonly(PluggableAuthService._doChangeGroup)
PluggableAuthService.userFolderEditGroup__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getGroups(self):
    gtool = getToolByName(self, 'portal_groups')
    return gtool.listGroups()
 
PluggableAuthService.getGroups = getGroups
PluggableAuthService.getGroups__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getGroupNames(self):
    gtool = getToolByName(self, 'portal_groups')
    return gtool.getGroupIds()
 
PluggableAuthService.getGroupNames = getGroupNames
PluggableAuthService.getGroupNames__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getGroupIds(self):
    gtool = getToolByName(self, 'portal_groups')
    return gtool.getGroupIds()
 
PluggableAuthService.getGroupIds = getGroupIds
PluggableAuthService.getGroupIds__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getGroup(self, group_id):
    """Like getGroupById in groups tool, but doesn't wrap.
    """
    group = None
    introspectors = self.plugins.listPlugins(IGroupIntrospection)
 
    if not introspectors:
        raise ValueError('No plugins allow for group management')
    for iid, introspector in introspectors:
        group = introspector.getGroupById(group_id)
        if group is not None:
            break
    return group
 
PluggableAuthService.getGroup = getGroup
PluggableAuthService.getGroup__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getGroupByName(self, name, default=None):
    ret = self.getGroup(name)
    if ret is None:
        return default
    return ret
 
PluggableAuthService.getGroupByName = getGroupByName
PluggableAuthService.getGroupByName__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getGroupById(self, id, default=None):
    gtool = getToolByName(self, "portal_groups")
    ret = gtool.getGroupById(id)
    if ret is None:
        return default
    else:
        return ret
 
PluggableAuthService.getGroupById = getGroupById
PluggableAuthService.getGroupById__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getLocalRolesForDisplay(self, object):
    """This is used for plone's local roles display
 
    This method returns a tuple (massagedUsername, roles, userType,
    actualUserName).  This method is protected by the 'access content
    information' permission. We may change that if it's too
    permissive...
 
    A GRUF method originally.
    """
    # Perform security check on destination object
    if not getSecurityManager().checkPermission(manage_properties, object):
        raise Unauthorized(name="getLocalRolesForDisplay")
 
    return self._getLocalRolesForDisplay(object)
 
PluggableAuthService.getLocalRolesForDisplay = getLocalRolesForDisplay
 
 
def _getLocalRolesForDisplay(self, object):
    result = []
    # we don't have a PAS-side way to get this
    local_roles = object.get_local_roles()
    for one_user in local_roles:
        username = userid = one_user[0]
        roles = one_user[1]
        userType = 'user'
        if self.getGroup(userid):
            userType = 'group'
        else:
            user = self.getUserById(userid) or self.getUser(username)
            if user:
                username = user.getUserName()
                userid = user.getId()
        result.append((username, roles, userType, userid))
    return tuple(result)
 
PluggableAuthService._getLocalRolesForDisplay = _getLocalRolesForDisplay
 
 
def getUsers(self):
    """
    Return a list of all users from plugins that implement the user
    introspection interface.
 
    Could potentially be very long.
    """
    # We should have a method that's cheap about returning number of users.
    retval = []
    try:
        introspectors = self.plugins.listPlugins(IUserIntrospection)
    except KeyError:
        return retval
 
    for iid, introspector in introspectors:
        retval += introspector.getUsers()
 
    return retval
 
PluggableAuthService.getUsers = getUsers
PluggableAuthService.getUsers__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
PluggableAuthService.getPureUsers = getUsers   # this'll make listMembers work
PluggableAuthService.getPureUsers__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def canListAllUsers(self):
    plugins = self._getOb('plugins')
 
    # Do we have multiple user plugins?
    if len(plugins.listPlugins(IUserEnumerationPlugin)) \
            != len(plugins.listPlugins(IUserIntrospection)):
        return False
    return True
 
PluggableAuthService.canListAllUsers = canListAllUsers
PluggableAuthService.canListAllUsers__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def canListAllGroups(self):
    plugins = self._getOb('plugins')
 
    # Do we have multiple user plugins?
    if len(plugins.listPlugins(IGroupEnumerationPlugin)) \
            != len(plugins.listPlugins(IGroupIntrospection)):
        return False
    return True
 
PluggableAuthService.canListAllGroups = canListAllGroups
PluggableAuthService.canListAllGroups__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def userSetPassword(self, userid, password):
    """Emulate GRUF 3 call for password set, for use with PwRT."""
    # used by _doChangeUser
    plugins = self._getOb('plugins')
    managers = plugins.listPlugins(IUserManagement)
 
    if not (managers):
        raise NotImplementedError("There is no plugin that can modify users")
 
    modified = False
    for mid, manager in managers:
        try:
            manager.doChangeUser(userid, password)
        except RuntimeError:
            # XXX: why silent ignore this Error?
            pass
        else:
            modified = True
 
    if not modified:
        raise RuntimeError("No user management plugins were able "
                           "to successfully modify the user")
 
PluggableAuthService.userSetPassword = userSetPassword
PluggableAuthService.userSetPassword__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def credentialsChanged(self, user, name, new_password):
    """Notifies the authentication mechanism that this user has changed
    passwords.  This can be used to update the authentication cookie.
    Note that this call should *not* cause any change at all to user
    databases.
 
    For use by CMFCore.MembershipTool.credentialsChanged
    """
    request = self.REQUEST
    response = request.RESPONSE
    login = name
 
    self.updateCredentials(request, response, login, new_password)
 
PluggableAuthService.credentialsChanged = credentialsChanged
PluggableAuthService.credentialsChanged__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
# for ZopeVersionControl, we need to check 'plugins' for more than
# existence, since it replaces objects (like 'plugins') with SimpleItems
# and calls _delOb, which tries to use special methods of 'plugins'
from OFS.Folder import Folder
 
 
def _delOb(self, id):
    #
    #   Override ObjectManager's version to clean up any plugin
    #   registrations for the deleted object
    #
    # XXX imo this is a evil one
    #
    plugins = self._getOb('plugins', None)
 
    if getattr(plugins, 'removePluginById', None) is not None:
        plugins.removePluginById(id)
 
    Folder._delOb(self, id)
 
PluggableAuthService._delOb = _delOb
 
 
def addRole(self, role):
    plugins = self._getOb('plugins')
    roles = plugins.listPlugins(IRoleAssignerPlugin)
 
    for plugin_id, plugin in roles:
        try:
            plugin.addRole(role)
            return
        except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
            pass
 
PluggableAuthService.addRole = addRole
PluggableAuthService.addRole__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))
 
 
def getAllLocalRoles(self, context):
    # Perform security check on destination object
    if not getSecurityManager().checkPermission(change_permissions, context):
        raise Unauthorized(name="getAllLocalRoles")
    return self._getAllLocalRoles(context)
 
PluggableAuthService.getAllLocalRoles = getAllLocalRoles
 
 
def _getAllLocalRoles(self, context):
    plugins = self._getOb('plugins')
    lrmanagers = plugins.listPlugins(ILocalRolesPlugin)
 
    roles = {}
    for lrid, lrmanager in lrmanagers:
        newroles = lrmanager.getAllLocalRolesInContext(context)
        for k, v in newroles.items():
            if k not in roles:
                roles[k] = set()
            roles[k].update(v)
 
    return roles
 
PluggableAuthService._getAllLocalRoles = _getAllLocalRoles