############################################################################## # # PlonePAS - Adapt PluggableAuthService for use in Plone # Copyright (C) 2005 Enfold Systems, Kapil Thangavelu, et al # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this # distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ """ import logging from sets import Set from Acquisition import aq_base from AccessControl import ClassSecurityInfo from AccessControl.requestmethod import postonly from Globals import InitializeClass from zope.interface import implementedBy from zope.deprecation import deprecate from Products.CMFCore.utils import registerToolInterface from Products.CMFPlone.GroupsTool import GroupsTool as PloneGroupsTool from Products.PlonePAS.interfaces import group as igroup from Products.PluggableAuthService.interfaces.plugins import IRoleAssignerPlugin from Products.PluggableAuthService.utils import classImplements from Products.PluggableAuthService.PluggableAuthService import \ _SWALLOWABLE_PLUGIN_EXCEPTIONS from Products.GroupUserFolder.GroupsToolPermissions import ViewGroups, DeleteGroups, ManageGroups log = logging.getLogger('PluggableAuthService').exception class NotSupported(Exception): pass class GroupsTool(PloneGroupsTool): """ Replace the GRUF groups tool with PAS-specific methods. """ id = 'portal_groups' meta_type = 'PlonePAS Groups Tool' security = ClassSecurityInfo() toolicon = 'tool.gif' ## # basic group mgmt ## security.declarePrivate('invalidateGroup') def invalidateGroup(self, id): view_name = '_findGroup-%s' % id self.acl_users.ZCacheable_invalidate(view_name=view_name) @postonly def addGroup(self, id, roles = [], groups = [], properties=None, REQUEST=None, *args, **kw): group = None success = 0 managers = self._getGroupManagers() if roles is None: roles = [] if groups is None: groups = [] # Check to see if a user with the id already exists fail if it does results = self.acl_users.searchPrincipals(id=id, exact_match=True) if results: return 0 if not managers: raise NotSupported, 'No plugins allow for group management' for mid, manager in managers: success = manager.addGroup(id, title=kw.get('title', id), description=kw.get('title', None)) if success: self.setRolesForGroup(id, roles) for g in groups: manager.addPrincipalToGroup(g, id) break if success: group = self.getGroupById(id) group.setGroupProperties(properties or kw) self.createGrouparea(id) return success @postonly def editGroup(self, id, roles=None, groups=None, REQUEST=None, *args, **kw): """Edit the given group with the supplied roles. Passwords for groups seem to be irrelevant. PlonePAS doesn't deal with domains either. If user is not present, returns without exception. """ g = self.getGroupById(id) if not g: raise KeyError, 'Trying to edit a non-existing group: %s' % id if roles: self.setRolesForGroup(id, roles) g.setGroupProperties(kw) if groups: # remove absent groups groupset = Set(groups) p_groups = Set(self.getGroupsForPrincipal(g)) rmgroups = p_groups - groupset for gid in rmgroups: self.removePrincipalFromGroup(g, gid) # add groups try: groupmanagers = self._getGroupManagers() except _SWALLOWABLE_PLUGIN_EXCEPTIONS: log('Plugin listing error') groupmanagers = () for group in groups: for gm_id, gm in groupmanagers: try: if gm.addPrincipalToGroup(id, group): break except _SWALLOWABLE_PLUGIN_EXCEPTIONS: log('AuthenticationPlugin %s error' % gm_id) security.declareProtected(DeleteGroups, 'removeGroup') @postonly def removeGroup(self, group_id, keep_workspaces=0, REQUEST=None): """Remove a single group, including group workspace, unless keep_workspaces==true. """ retval = False managers = self._getGroupManagers() if not managers: raise NotSupported, 'No plugins allow for group management' for mid, manager in managers: if manager.removeGroup(group_id): retval = True gwf = self.getGroupWorkspacesFolder() if retval and gwf and not keep_workspaces: grouparea = self.getGroupareaFolder(group_id) if grouparea is not None: workspace_id = grouparea.getId() if hasattr(aq_base(gwf), workspace_id): gwf._delObject(workspace_id) self.invalidateGroup(group_id) return retval security.declareProtected(DeleteGroups, 'removeGroups') @postonly def removeGroups(self, ids, keep_workspaces=0, REQUEST=None): """Remove the group in the provided list (if possible). Will by default remove this group's GroupWorkspace if it exists. You may turn this off by specifying keep_workspaces=true. """ for id in ids: self.removeGroup(id, keep_workspaces) security.declareProtected(ManageGroups, 'setRolesForGroup') @postonly def setRolesForGroup(self, group_id, roles=(), REQUEST=None): rmanagers = self._getPlugins().listPlugins(IRoleAssignerPlugin) if not (rmanagers): raise NotImplementedError, ('There is no plugin that can ' 'assign roles to groups') for rid, rmanager in rmanagers: rmanager.assignRolesToPrincipal(roles, group_id) self.invalidateGroup(group_id) ## # basic principal mgmt ## security.declareProtected(ManageGroups, 'addPrincipalToGroup') @postonly def addPrincipalToGroup(self, principal_id, group_id, REQUEST=None): managers = self._getGroupManagers() if not managers: raise NotSupported, 'No plugins allow for group management' for mid, manager in managers: if manager.addPrincipalToGroup(principal_id, group_id): return True return False security.declareProtected(ManageGroups, 'removePrincipalFromGroup') @postonly def removePrincipalFromGroup(self, principal_id, group_id, REQUEST=None): managers = self._getGroupManagers() if not managers: raise NotSupported, 'No plugins allow for group management' for mid, manager in managers: if manager.removePrincipalFromGroup(principal_id, group_id): return True return False ## # group getters ## def getGroupById(self, group_id): group = self.acl_users.getGroup(group_id) if group is not None: group = self.wrapGroup(group) return group security.declareProtected(ManageGroups, 'searchGroups') @deprecate("portal_groups.searchForGroups is deprecated and will " "be removed in Plone 3.5. Use PAS searchGroups instead") def searchGroups(self, *args, **kw): # XXX document interface.. returns a list of dictionaries return self.acl_users.searchGroups(*args, **kw) @deprecate("portal_groups.searchForGroups is deprecated and will " "be removed in Plone 3.5. Use PAS searchGroups instead") def searchForGroups(self, REQUEST={}, **kw): """Search for groups by keyword. The following properties can be searched: - name #- email #- title Only id/title search is implemented for groups. Is the rest of this junk used anywhere? This is an 'AND' request. When it takes 'name' as keyword (or in REQUEST) and searches on Full name and id. Simple name searches are "fast". """ acl_users = self.acl_users groups_tool = self.portal_groups if REQUEST: dict = REQUEST else: dict = kw name = dict.get('name', None) #email = dict.get('email', None) #roles = dict.get('roles', None) #title = dict.get('title', None) title_or_name = dict.get('title_or_name', None) if name: name = name.strip().lower() if not name: name = None if title_or_name: name = title_or_name #if email: # email = email.strip().lower() #if not email: # email = None #if title: # title = title.strip().lower() #if not title: # title = None md_groups = [] uf_groups = [] if name: # This will allow us to retrieve groups by their id only uf_groups = acl_users.searchGroups(id=name) # PAS allows search to return dupes. We must winnow... uf_groups_new = [] for group in uf_groups: if group not in uf_groups_new: uf_groups_new.append(group) uf_groups = uf_groups_new groups = [] if md_groups or uf_groups: getGroupById = self.getGroupById for groupid in md_groups: groups.append(getGroupById(groupid)) for group in uf_groups: groupid = group['groupid'] if groupid in md_groups: continue # Kill dupes groups.append(getGroupById(groupid)) #if not email and \ # not roles and \ # not last_login_time: # return groups return groups @deprecate("portal_groups.listGroups is deprecated and will " "be removed in Plone 3.5. Use PAS searchGroups instead") def listGroups(self): # potentially not all groups may be found by this interface # if the underlying group source doesn't support introspection groups = [] introspectors = self._getGroupIntrospectors() for iid, introspector in introspectors: groups.extend(introspector.getGroups()) return [self.wrapGroup(elt) for elt in groups] security.declareProtected(ViewGroups, 'getGroupIds') @deprecate("portal_groups.getGroupIds is deprecated and will " "be removed in Plone 3.5. Use PAS searchGroups instead") def getGroupIds(self): groups = [] introspectors = self._getGroupIntrospectors() for iid, introspector in introspectors: groups.extend(introspector.getGroupIds()) return groups listGroupIds = getGroupIds security.declareProtected(ViewGroups, 'getGroupMembers') @deprecate("portal_groups.getGroupMembers is deprecated and will " "be removed in Plone 3.5. Use PAS to get a group and check " "its members instead.") def getGroupMembers(self, group_id): members = [] introspectors = self._getGroupIntrospectors() for iid, introspector in introspectors: members = introspector.getGroupMembers(group_id) if members: break return members security.declareProtected(ViewGroups, 'getGroupsForPrincipal') @deprecate("portal_groups.getGroupsForPrincipal is deprecated and will " "be removed in Plone 3.5. Use PAS to get a principal and check " "its group list instead.") def getGroupsForPrincipal(self, principal): introspectors = self._getGroupIntrospectors() for iid, introspector in introspectors: groups = introspector.getGroupsForPrincipal(principal) if groups: break return groups ## # plugin getters ## security.declarePrivate('_getPlugins') def _getPlugins(self): return self.acl_users.plugins security.declarePrivate('_getGroupManagers') def _getGroupManagers(self): return self._getPlugins().listPlugins( igroup.IGroupManagement ) security.declarePrivate('_getGroupIntrospectors') def _getGroupIntrospectors(self): return self._getPlugins().listPlugins( igroup.IGroupIntrospection ) security.declarePrivate('_getGroupSpaceManagers') def _getGroupSpaceManagers(self): return self._getPlugins().listPlugins( igroup.IGroupSpaceManagers ) classImplements(GroupsTool, *tuple(implementedBy(PloneGroupsTool)) + (igroup.IGroupTool,)) InitializeClass(GroupsTool) registerToolInterface('portal_groups', igroup.IGroupTool)