Back to index

plone3  3.1.7
GroupsTool.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 ## GroupUserFolder
00003 ## Copyright (C)2006 Ingeniweb
00004 
00005 ## This program is free software; you can redistribute it and/or modify
00006 ## it under the terms of the GNU General Public License as published by
00007 ## the Free Software Foundation; either version 2 of the License, or
00008 ## (at your option) any later version.
00009 
00010 ## This program is distributed in the hope that it will be useful,
00011 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 ## GNU General Public License for more details.
00014 
00015 ## You should have received a copy of the GNU General Public License
00016 ## along with this program; see the file COPYING. If not, write to the
00017 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00018 
00019 ## Copyright (c) 2003 The Connexions Project, All Rights Reserved
00020 ## initially written by J Cameron Cooper, 11 June 2003
00021 ## concept with Brent Hendricks, George Runyan
00022 """
00023 Basic usergroup tool.
00024 """
00025 __version__ = "$Revision$"
00026 # $Source:  $
00027 # $Id: GroupsTool.py 50142 2007-09-25 13:13:12Z wichert $
00028 __docformat__ = 'restructuredtext'
00029 
00030 from Products.CMFCore.utils import UniqueObject
00031 from Products.CMFCore.utils import getToolByName
00032 from Products.CMFCore.utils import _checkPermission
00033 from OFS.SimpleItem import SimpleItem
00034 from Globals import InitializeClass, DTMLFile, MessageDialog
00035 from Acquisition import aq_base
00036 from AccessControl.User import nobody
00037 from AccessControl import ClassSecurityInfo
00038 from ZODB.POSException import ConflictError
00039 # BBB CMF < 1.5
00040 try:
00041     from Products.CMFCore.permissions import ManagePortal
00042     from Products.CMFCore.permissions import View
00043     from Products.CMFCore.permissions import ViewManagementScreens
00044 except ImportError:
00045     from Products.CMFCore.CMFCorePermissions import ManagePortal
00046     from Products.CMFCore.CMFCorePermissions import View
00047     from Products.CMFCore.CMFCorePermissions import ViewManagementScreens
00048 
00049 from Products.GroupUserFolder import postonly
00050 from GroupsToolPermissions import AddGroups
00051 from GroupsToolPermissions import ManageGroups
00052 from GroupsToolPermissions import DeleteGroups
00053 from GroupsToolPermissions import ViewGroups
00054 from GroupsToolPermissions import SetGroupOwnership
00055 from Products.CMFCore.ActionProviderBase import ActionProviderBase
00056 from interfaces.portal_groups import portal_groups as IGroupsTool
00057 from global_symbols import *
00058 
00059 # Optional feature-preview support
00060 import PloneFeaturePreview
00061 
00062 class GroupsTool (UniqueObject, SimpleItem, ActionProviderBase, ):
00063     """ This tool accesses group data through a GRUF acl_users object.
00064 
00065     It can be replaced with something that groups member data in a
00066     different way.
00067     """
00068     # Show implementation only if  IGroupsTool is defined
00069     # The latter will work only with Plone 1.1 => hence, the if
00070     if hasattr(ActionProviderBase, '__implements__'):
00071         __implements__ = (IGroupsTool, ActionProviderBase.__implements__)
00072 
00073     id = 'portal_groups'
00074     meta_type = 'CMF Groups Tool'
00075     _actions = ()
00076 
00077     security = ClassSecurityInfo()
00078 
00079     groupworkspaces_id = "groups"
00080     groupworkspaces_title = "Groups"
00081     groupWorkspacesCreationFlag = 1
00082     groupWorkspaceType = "Folder"
00083     groupWorkspaceContainerType = "Folder"
00084 
00085     manage_options=(
00086             ( { 'label' : 'Configure'
00087                      , 'action' : 'manage_config'
00088                     },
00089                 ) + ActionProviderBase.manage_options +
00090                 ( { 'label' : 'Overview'
00091                      , 'action' : 'manage_overview'
00092                      },
00093                 ) + SimpleItem.manage_options)
00094 
00095     #                                                   #
00096     #                   ZMI methods                     #
00097     #                                                   #
00098     security.declareProtected(ViewManagementScreens, 'manage_overview')
00099     manage_overview = DTMLFile('dtml/explainGroupsTool', globals())     # unlike MembershipTool
00100     security.declareProtected(ViewManagementScreens, 'manage_config')
00101     manage_config = DTMLFile('dtml/configureGroupsTool', globals())
00102 
00103     security.declareProtected(ManagePortal, 'manage_setGroupWorkspacesFolder')
00104     def manage_setGroupWorkspacesFolder(self, id='groups', title='Groups', REQUEST=None):
00105         """ZMI method for workspace container name set."""
00106         self.setGroupWorkspacesFolder(id, title)
00107         return self.manage_config(manage_tabs_message="Workspaces folder name set to %s" % id)
00108 
00109     security.declareProtected(ManagePortal, 'manage_setGroupWorkspaceType')
00110     def manage_setGroupWorkspaceType(self, type='Folder', REQUEST=None):
00111         """ZMI method for workspace type set."""
00112         self.setGroupWorkspaceType(type)
00113         return self.manage_config(manage_tabs_message="Group Workspaces type set to %s" % type)
00114 
00115     security.declareProtected(ManagePortal, 'manage_setGroupWorkspaceContainerType')
00116     def manage_setGroupWorkspaceContainerType(self, type='Folder', REQUEST=None):
00117         """ZMI method for workspace type set."""
00118         self.setGroupWorkspaceContainerType(type)
00119         return self.manage_config(manage_tabs_message="Group Workspaces container type set to %s" % type)
00120 
00121     security.declareProtected(ViewGroups, 'getGroupById')
00122     def getGroupById(self, id):
00123         """
00124         Returns the portal_groupdata-ish object for a group corresponding to this id.
00125         """
00126         if id==None:
00127             return None
00128         g = self.acl_users.getGroupByName(id, None)
00129         if g is not None:
00130             g = self.wrapGroup(g)
00131         return g
00132 
00133     security.declareProtected(ViewGroups, 'getGroupsByUserId')
00134     def getGroupsByUserId(self, userid):
00135         """Return a list of the groups the user corresponding to 'userid' belongs to."""
00136         #log("getGroupsByUserId(%s)" % userid)
00137         user = self.acl_users.getUser(userid)
00138         #log("user '%s' is in groups %s" % (userid, user.getGroups()))
00139         if user:
00140             groups = user.getGroups() or []
00141         else:
00142             groups = []
00143         return [self.getGroupById(elt) for elt in groups]
00144 
00145     security.declareProtected(ViewGroups, 'listGroups')
00146     def listGroups(self):
00147         """Return a list of the available portal_groupdata-ish objects."""
00148         return [ self.wrapGroup(elt) for elt in self.acl_users.getGroups() ]
00149 
00150     security.declareProtected(ViewGroups, 'listGroupIds')
00151     def listGroupIds(self):
00152         """Return a list of the available groups' ids as entered (without group prefixes)."""
00153         return self.acl_users.getGroupNames()
00154 
00155     security.declareProtected(ViewGroups, 'listGroupNames')
00156     def listGroupNames(self):
00157         """Return a list of the available groups' ids as entered (without group prefixes)."""
00158         return self.acl_users.getGroupNames()
00159 
00160     security.declarePublic("isGroup")
00161     def isGroup(self, u):
00162         """Test if a user/group object is a group or not.
00163         You must pass an object you get earlier with wrapUser() or wrapGroup()
00164         """
00165         base = aq_base(u)
00166         if hasattr(base, "isGroup") and base.isGroup():
00167             return 1
00168         return 0
00169 
00170     security.declareProtected(View, 'searchForGroups')
00171     def searchForGroups(self, REQUEST = {}, **kw):
00172         """Return a list of groups meeting certain conditions. """
00173         # arguments need to be better refined?
00174         if REQUEST:
00175             dict = REQUEST
00176         else:
00177             dict = kw
00178 
00179         name = dict.get('name', None)
00180         email = dict.get('email', None)
00181         roles = dict.get('roles', None)
00182         title = dict.get('title', None)
00183         title_or_name = dict.get('title_or_name', None)
00184         
00185         last_login_time = dict.get('last_login_time', None)
00186         #is_manager = self.checkPermission('Manage portal', self)
00187 
00188         if name:
00189             name = name.strip().lower()
00190         if not name:
00191             name = None
00192         if email:
00193             email = email.strip().lower()
00194         if not email:
00195             email = None
00196         if title:
00197             title = title.strip().lower()
00198         if title_or_name:
00199             title_or_name = title_or_name.strip().lower()
00200         if not title:
00201             title = None
00202 
00203         res = []
00204         portal = self.portal_url.getPortalObject()
00205         for g in portal.portal_groups.listGroups():
00206             #if not (g.listed or is_manager):
00207             #    continue
00208             if name:
00209                 if (g.getGroupName().lower().find(name) == -1) and (g.getGroupId().lower().find(name) == -1):
00210                     continue
00211             if email:
00212                 if g.email.lower().find(email) == -1:
00213                     continue
00214             if roles:
00215                 group_roles = g.getRoles()
00216                 found = 0
00217                 for r in roles:
00218                     if r in group_roles:
00219                         found = 1
00220                         break
00221                 if not found:
00222                     continue
00223             if title:
00224                 if g.title.lower().find(title) == -1:
00225                     continue
00226             if title_or_name:
00227                 # first search for title
00228                 if g.title.lower().find(title_or_name) == -1:
00229                     # not found, now search for name
00230                     if (g.getGroupName().lower().find(title_or_name) == -1) and (g.getGroupId().lower().find(title_or_name) == -1):
00231                         continue
00232                 
00233             if last_login_time:
00234                 if g.last_login_time < last_login_time:
00235                     continue
00236             res.append(g)
00237 
00238         return res
00239 
00240     security.declareProtected(AddGroups, 'addGroup')
00241     def addGroup(self, id, roles = [], groups = [], REQUEST=None, *args, **kw):
00242         """Create a group, and a group workspace if the toggle is on, with the supplied id, roles, and domains.
00243 
00244         Underlying user folder must support adding users via the usual Zope API.
00245         Passwords for groups ARE irrelevant in GRUF."""
00246         if id in self.listGroupIds():
00247             raise ValueError, "Group '%s' already exists." % (id, )
00248         self.acl_users.userFolderAddGroup(id, roles = roles, groups = groups )
00249         self.createGrouparea(id)
00250         self.getGroupById(id).setProperties(**kw)
00251     addGroup = postonly(addGroup)
00252 
00253     security.declareProtected(ManageGroups, 'editGroup')
00254     def editGroup(self, id, roles = None, groups = None, REQUEST=None, *args, **kw):
00255         """Edit the given group with the supplied password, roles, and domains.
00256 
00257         Underlying user folder must support editing users via the usual Zope API.
00258         Passwords for groups seem to be currently irrelevant in GRUF."""
00259         self.acl_users.userFolderEditGroup(id, roles = roles, groups = groups, )
00260         self.getGroupById(id).setProperties(**kw)
00261     editGroup = postonly(editGroup)
00262 
00263     security.declareProtected(DeleteGroups, 'removeGroups')
00264     def removeGroups(self, ids, keep_workspaces=0, REQUEST=None):
00265         """Remove the group in the provided list (if possible).
00266 
00267         Will by default remove this group's GroupWorkspace if it exists. You may
00268         turn this off by specifying keep_workspaces=true.
00269         Underlying user folder must support removing users via the usual Zope API."""
00270         for gid in ids:
00271             gdata = self.getGroupById(gid)
00272             gusers = gdata.getGroupMembers()
00273             for guser in gusers:
00274                 gdata.removeMember(guser.id)
00275 
00276         self.acl_users.userFolderDelGroups(ids)
00277         gwf = self.getGroupWorkspacesFolder()
00278         if not gwf: # _robert_
00279             return
00280         if not keep_workspaces:
00281             for id in ids:
00282                 if hasattr(aq_base(gwf), id):
00283                     gwf._delObject(id)
00284     removeGroups = postonly(removeGroups)
00285 
00286     security.declareProtected(SetGroupOwnership, 'setGroupOwnership')
00287     def setGroupOwnership(self, group, object, REQUEST=None):
00288         """Make the object 'object' owned by group 'group' (a portal_groupdata-ish object).
00289 
00290         For GRUF this is easy. Others may have to re-implement."""
00291         user = group.getGroup()
00292         if user is None:
00293             raise ValueError, "Invalid group: '%s'." % (group, )
00294         object.changeOwnership(user)
00295         object.manage_setLocalRoles(user.getId(), ['Owner'])
00296     setGroupOwnership = postonly(setGroupOwnership)
00297 
00298     security.declareProtected(ManagePortal, 'setGroupWorkspacesFolder')
00299     def setGroupWorkspacesFolder(self, id="", title=""):
00300         """ Set the location of the Group Workspaces folder by id.
00301 
00302         The Group Workspaces Folder contains all the group workspaces, just like the
00303         Members folder contains all the member folders.
00304 
00305          If anyone really cares, we can probably make the id work as a path as well,
00306          but for the moment it's only an id for a folder in the portal root, just like the
00307          corresponding MembershipTool functionality. """
00308         self.groupworkspaces_id = id.strip()
00309         self.groupworkspaces_title = title
00310 
00311     security.declareProtected(ManagePortal, 'getGroupWorkspacesFolderId')
00312     def getGroupWorkspacesFolderId(self):
00313         """ Get the Group Workspaces folder object's id.
00314 
00315         The Group Workspaces Folder contains all the group workspaces, just like the
00316         Members folder contains all the member folders. """
00317         return self.groupworkspaces_id
00318 
00319     security.declareProtected(ManagePortal, 'getGroupWorkspacesFolderTitle')
00320     def getGroupWorkspacesFolderTitle(self):
00321         """ Get the Group Workspaces folder object's title.
00322         """
00323         return self.groupworkspaces_title
00324 
00325     security.declarePublic('getGroupWorkspacesFolder')
00326     def getGroupWorkspacesFolder(self):
00327         """ Get the Group Workspaces folder object.
00328 
00329         The Group Workspaces Folder contains all the group workspaces, just like the
00330         Members folder contains all the member folders. """
00331         parent = self.aq_inner.aq_parent
00332         folder = getattr(parent, self.getGroupWorkspacesFolderId(), None)
00333         return folder
00334 
00335     security.declareProtected(ManagePortal, 'toggleGroupWorkspacesCreation')
00336     def toggleGroupWorkspacesCreation(self, REQUEST=None):
00337         """ Toggles the flag for creation of a GroupWorkspaces folder upon creation of the group. """
00338         if not hasattr(self, 'groupWorkspacesCreationFlag'):
00339             self.groupWorkspacesCreationFlag = 0
00340 
00341         self.groupWorkspacesCreationFlag = not self.groupWorkspacesCreationFlag
00342 
00343         m = self.groupWorkspacesCreationFlag and 'turned on' or 'turned off'
00344 
00345         return self.manage_config(manage_tabs_message="Workspaces creation %s" % m)
00346 
00347     security.declareProtected(ManagePortal, 'getGroupWorkspacesCreationFlag')
00348     def getGroupWorkspacesCreationFlag(self):
00349         """Return the (boolean) flag indicating whether the Groups Tool will create a group workspace
00350         upon the creation of the group (if one doesn't exist already). """
00351         return self.groupWorkspacesCreationFlag
00352 
00353     security.declareProtected(AddGroups, 'createGrouparea')
00354     def createGrouparea(self, id):
00355         """Create a space in the portal for the given group, much like member home
00356         folders."""
00357         parent = self.aq_inner.aq_parent
00358         workspaces = self.getGroupWorkspacesFolder()
00359         pt = getToolByName( self, 'portal_types' )
00360 
00361         if id and self.getGroupWorkspacesCreationFlag():
00362             if workspaces is None:
00363                 # add GroupWorkspaces folder
00364                 pt.constructContent(
00365                     type_name = self.getGroupWorkspaceContainerType(),
00366                     container = parent,
00367                     id = self.getGroupWorkspacesFolderId(),
00368                     )
00369                 workspaces = self.getGroupWorkspacesFolder()
00370                 workspaces.setTitle(self.getGroupWorkspacesFolderTitle())
00371                 workspaces.setDescription("Container for " + self.getGroupWorkspacesFolderId())
00372                 # how about ownership?
00373 
00374                 # this stuff like MembershipTool...
00375                 portal_catalog = getToolByName( self, 'portal_catalog' )
00376                 portal_catalog.unindexObject(workspaces)     # unindex GroupWorkspaces folder
00377                 workspaces._setProperty('right_slots', (), 'lines')
00378                 
00379             if workspaces is not None and not hasattr(workspaces.aq_base, id):
00380                 # add workspace to GroupWorkspaces folder
00381                 pt.constructContent(
00382                     type_name = self.getGroupWorkspaceType(),
00383                     container = workspaces,
00384                     id = id,
00385                     )
00386                 space = self.getGroupareaFolder(id)
00387                 space.setTitle("%s workspace" % id)
00388                 space.setDescription("Container for objects shared by this group")
00389 
00390                 if hasattr(space, 'setInitialGroup'):
00391                     # GroupSpaces can have their own policies regarding the group
00392                     # that they are created for.
00393                     user = self.getGroupById(id).getGroup()
00394                     if user is not None:
00395                         space.setInitialGroup(user)
00396                 else:
00397                     space.manage_delLocalRoles(space.users_with_local_role('Owner'))
00398                     self.setGroupOwnership(self.getGroupById(id), space)
00399 
00400                 # Hook to allow doing other things after grouparea creation.
00401                 notify_script = getattr(workspaces, 'notifyGroupAreaCreated', None)
00402                 if notify_script is not None:
00403                     notify_script()
00404 
00405                 # Re-indexation
00406                 portal_catalog = getToolByName( self, 'portal_catalog' )
00407                 portal_catalog.reindexObject(space)
00408  
00409     security.declareProtected(ManagePortal, 'getGroupWorkspaceType')
00410     def getGroupWorkspaceType(self):
00411         """Return the Type (as in TypesTool) to make the GroupWorkspace."""
00412         return self.groupWorkspaceType
00413 
00414     security.declareProtected(ManagePortal, 'setGroupWorkspaceType')
00415     def setGroupWorkspaceType(self, type):
00416         """Set the Type (as in TypesTool) to make the GroupWorkspace."""
00417         self.groupWorkspaceType = type
00418 
00419     security.declareProtected(ManagePortal, 'getGroupWorkspaceContainerType')
00420     def getGroupWorkspaceContainerType(self):
00421         """Return the Type (as in TypesTool) to make the GroupWorkspace."""
00422         return self.groupWorkspaceContainerType
00423 
00424     security.declareProtected(ManagePortal, 'setGroupWorkspaceContainerType')
00425     def setGroupWorkspaceContainerType(self, type):
00426         """Set the Type (as in TypesTool) to make the GroupWorkspace."""
00427         self.groupWorkspaceContainerType = type
00428 
00429     security.declarePublic('getGroupareaFolder')
00430     def getGroupareaFolder(self, id=None, verifyPermission=0):
00431         """Returns the object of the group's work area."""
00432         if id is None:
00433             group = self.getAuthenticatedMember()
00434             if not hasattr(member, 'getGroupId'):
00435                 return None
00436             id = group.getGroupId()
00437         workspaces = self.getGroupWorkspacesFolder()
00438         if workspaces:
00439             try:
00440                 folder = workspaces[id]
00441                 if verifyPermission and not _checkPermission('View', folder):
00442                     # Don't return the folder if the user can't get to it.
00443                     return None
00444                 return folder
00445             except KeyError: pass
00446         return None
00447 
00448     security.declarePublic('getGroupareaURL')
00449     def getGroupareaURL(self, id=None, verifyPermission=0):
00450         """Returns the full URL to the group's work area."""
00451         ga = self.getGroupareaFolder(id, verifyPermission)
00452         if ga is not None:
00453             return ga.absolute_url()
00454         else:
00455             return None
00456 
00457     security.declarePrivate('wrapGroup')
00458     def wrapGroup(self, g, wrap_anon=0):
00459         ''' Sets up the correct acquisition wrappers for a group
00460         object and provides an opportunity for a portal_memberdata
00461         tool to retrieve and store member data independently of
00462         the user object.
00463         '''
00464         b = getattr(g, 'aq_base', None)
00465         if b is None:
00466             # u isn't wrapped at all.  Wrap it in self.acl_users.
00467             b = g
00468             g = g.__of__(self.acl_users)
00469         if (b is nobody and not wrap_anon) or hasattr(b, 'getMemberId'):
00470             # This user is either not recognized by acl_users or it is
00471             # already registered with something that implements the
00472             # member data tool at least partially.
00473             return g
00474 
00475         parent = self.aq_inner.aq_parent
00476         base = getattr(parent, 'aq_base', None)
00477         if hasattr(base, 'portal_groupdata'):
00478             # Get portal_groupdata to do the wrapping.
00479             Log(LOG_DEBUG, "parent", parent)
00480             gd = getToolByName(parent, 'portal_groupdata')
00481             Log(LOG_DEBUG, "group data", gd)
00482             try:
00483                 #log("wrapping group %s" % g)
00484                 portal_group = gd.wrapGroup(g)
00485                 return portal_group
00486             except ConflictError:
00487                 raise
00488             except:
00489                 import logging
00490                 logger = logging.getLogger('GroupUserFolder.GroupsTool')
00491                 logger.exception('Error during wrapGroup')
00492         # Failed.
00493         return g
00494 
00495 InitializeClass(GroupsTool)