Back to index

plone3  3.1.7
groups.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # PlonePAS - Adapt PluggableAuthService for use in Plone
00004 # Copyright (C) 2005 Enfold Systems, Kapil Thangavelu, et al
00005 #
00006 # This software is subject to the provisions of the Zope Public License,
00007 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this
00008 # distribution.
00009 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00010 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00011 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00012 # FOR A PARTICULAR PURPOSE.
00013 #
00014 ##############################################################################
00015 """
00016 """
00017 import logging
00018 from sets import Set
00019 
00020 from Acquisition import aq_base
00021 from AccessControl import ClassSecurityInfo
00022 from AccessControl.requestmethod import postonly
00023 from Globals import InitializeClass
00024 
00025 from zope.interface import implementedBy
00026 from zope.deprecation import deprecate
00027 
00028 from Products.CMFCore.utils import registerToolInterface
00029 from Products.CMFPlone.GroupsTool import GroupsTool as PloneGroupsTool
00030 
00031 from Products.PlonePAS.interfaces import group as igroup
00032 from Products.PluggableAuthService.interfaces.plugins import IRoleAssignerPlugin
00033 from Products.PluggableAuthService.utils import classImplements
00034 from Products.PluggableAuthService.PluggableAuthService import \
00035                                     _SWALLOWABLE_PLUGIN_EXCEPTIONS
00036 from Products.GroupUserFolder.GroupsToolPermissions import ViewGroups, DeleteGroups, ManageGroups
00037 
00038 log = logging.getLogger('PluggableAuthService').exception
00039 
00040 class NotSupported(Exception): pass
00041 
00042 class GroupsTool(PloneGroupsTool):
00043     """
00044     Replace the GRUF groups tool with PAS-specific methods.
00045     """
00046 
00047     id = 'portal_groups'
00048     meta_type = 'PlonePAS Groups Tool'
00049     security = ClassSecurityInfo()
00050     toolicon = 'tool.gif'
00051 
00052     ##
00053     # basic group mgmt
00054     ##
00055 
00056     security.declarePrivate('invalidateGroup')
00057     def invalidateGroup(self, id):
00058         view_name = '_findGroup-%s' % id
00059         self.acl_users.ZCacheable_invalidate(view_name=view_name)
00060 
00061     @postonly
00062     def addGroup(self, id, roles = [], groups = [], properties=None, 
00063                  REQUEST=None, *args, **kw):
00064         group = None
00065         success = 0
00066         managers = self._getGroupManagers()
00067         if roles is None:
00068             roles = []
00069         if groups is None:
00070             groups = []
00071 
00072         # Check to see if a user with the id already exists fail if it does
00073         results = self.acl_users.searchPrincipals(id=id, exact_match=True)
00074         if results:
00075             return 0
00076 
00077         if not managers:
00078             raise NotSupported, 'No plugins allow for group management'
00079         for mid, manager in managers:
00080             success = manager.addGroup(id, title=kw.get('title', id),
00081                                        description=kw.get('title', None))
00082             if success:
00083                 self.setRolesForGroup(id, roles)
00084                 for g in groups:
00085                     manager.addPrincipalToGroup(g, id)
00086                 break
00087 
00088         if success:
00089             group = self.getGroupById(id)
00090             group.setGroupProperties(properties or kw)
00091             self.createGrouparea(id)
00092 
00093         return success
00094 
00095     @postonly
00096     def editGroup(self, id, roles=None, groups=None, REQUEST=None, *args, **kw):
00097         """Edit the given group with the supplied roles.
00098 
00099         Passwords for groups seem to be irrelevant.
00100         PlonePAS doesn't deal with domains either.
00101 
00102         If user is not present, returns without exception.
00103         """
00104         g = self.getGroupById(id)
00105         if not g:
00106             raise KeyError, 'Trying to edit a non-existing group: %s' % id
00107 
00108         if roles is not None:
00109             self.setRolesForGroup(id, roles)
00110         g.setGroupProperties(kw)
00111         if groups:
00112             # remove absent groups
00113             groupset = Set(groups)
00114             p_groups = Set(self.getGroupsForPrincipal(g))
00115             rmgroups = p_groups - groupset
00116             for gid in rmgroups:
00117                 self.removePrincipalFromGroup(g, gid)
00118 
00119             # add groups
00120             try:
00121                 groupmanagers = self._getGroupManagers()
00122             except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00123                 log('Plugin listing error')
00124                 groupmanagers = ()
00125 
00126             for group in groups:
00127                 for gm_id, gm in groupmanagers:
00128                     try:
00129                         if gm.addPrincipalToGroup(id, group):
00130                             break
00131                     except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00132                         log('AuthenticationPlugin %s error' % gm_id)
00133 
00134     security.declareProtected(DeleteGroups, 'removeGroup')
00135     @postonly
00136     def removeGroup(self, group_id, keep_workspaces=0, REQUEST=None):
00137         """Remove a single group, including group workspace, unless
00138         keep_workspaces==true.
00139         """
00140         retval = False
00141         managers = self._getGroupManagers()
00142         if not managers:
00143             raise NotSupported, 'No plugins allow for group management'
00144 
00145         for mid, manager in managers:
00146             if manager.removeGroup(group_id):
00147                 retval = True
00148 
00149         gwf = self.getGroupWorkspacesFolder()
00150         if retval and gwf and not keep_workspaces:
00151             grouparea = self.getGroupareaFolder(group_id)
00152             if grouparea is not None:
00153                 workspace_id = grouparea.getId()
00154                 if hasattr(aq_base(gwf), workspace_id):
00155                     gwf._delObject(workspace_id)
00156 
00157         self.invalidateGroup(group_id)
00158         return retval
00159 
00160     security.declareProtected(DeleteGroups, 'removeGroups')
00161     @postonly
00162     def removeGroups(self, ids, keep_workspaces=0, REQUEST=None):
00163         """Remove the group in the provided list (if possible).
00164 
00165         Will by default remove this group's GroupWorkspace if it exists. You may
00166         turn this off by specifying keep_workspaces=true.
00167         """
00168         for id in ids:
00169             self.removeGroup(id, keep_workspaces)
00170 
00171     security.declareProtected(ManageGroups, 'setRolesForGroup')
00172     @postonly
00173     def setRolesForGroup(self, group_id, roles=(), REQUEST=None):
00174         rmanagers = self._getPlugins().listPlugins(IRoleAssignerPlugin)
00175         if not (rmanagers):
00176             raise NotImplementedError, ('There is no plugin that can '
00177                                         'assign roles to groups')
00178         for rid, rmanager in rmanagers:
00179             rmanager.assignRolesToPrincipal(roles, group_id)
00180 
00181         self.invalidateGroup(group_id)
00182     ##
00183     # basic principal mgmt
00184     ##
00185 
00186     security.declareProtected(ManageGroups, 'addPrincipalToGroup')
00187     @postonly
00188     def addPrincipalToGroup(self, principal_id, group_id, REQUEST=None):
00189         managers = self._getGroupManagers()
00190         if not managers:
00191             raise NotSupported, 'No plugins allow for group management'
00192         for mid, manager in managers:
00193             if manager.addPrincipalToGroup(principal_id, group_id):
00194                 return True
00195         return False
00196 
00197     security.declareProtected(ManageGroups, 'removePrincipalFromGroup')
00198     @postonly
00199     def removePrincipalFromGroup(self, principal_id, group_id, REQUEST=None):
00200         managers = self._getGroupManagers()
00201         if not managers:
00202             raise NotSupported, 'No plugins allow for group management'
00203         for mid, manager in managers:
00204             if manager.removePrincipalFromGroup(principal_id, group_id):
00205                 return True
00206         return False
00207 
00208 
00209     ##
00210     # group getters
00211     ##
00212 
00213     def getGroupById(self, group_id):
00214         group = self.acl_users.getGroup(group_id)
00215         if group is not None:
00216             group = self.wrapGroup(group)
00217         return group
00218 
00219     security.declareProtected(ManageGroups, 'searchGroups')
00220     @deprecate("portal_groups.searchForGroups is deprecated and will "
00221                "be removed in Plone 3.5. Use PAS searchGroups instead")
00222     def searchGroups(self, *args, **kw):
00223         # XXX document interface.. returns a list of dictionaries
00224         return self.acl_users.searchGroups(*args, **kw)
00225 
00226     @deprecate("portal_groups.searchForGroups is deprecated and will "
00227                "be removed in Plone 3.5. Use PAS searchGroups instead")
00228     def searchForGroups(self, REQUEST={}, **kw):
00229         """Search for groups by keyword.
00230         The following properties can be searched:
00231         - name
00232         #- email
00233         #- title
00234 
00235         Only id/title search is implemented for groups. Is the rest of
00236         this junk used anywhere?
00237 
00238         This is an 'AND' request.
00239 
00240         When it takes 'name' as keyword (or in REQUEST) and searches on
00241         Full name and id.
00242 
00243         Simple name searches are "fast".
00244         """
00245         acl_users = self.acl_users
00246         groups_tool = self.portal_groups
00247         if REQUEST:
00248             dict = REQUEST
00249         else:
00250             dict = kw
00251 
00252         name = dict.get('name', None)
00253         #email = dict.get('email', None)
00254         #roles = dict.get('roles', None)
00255         #title = dict.get('title', None)
00256         title_or_name = dict.get('title_or_name', None)
00257         if name:
00258             name = name.strip().lower()
00259         if not name:
00260             name = None
00261         if title_or_name: name = title_or_name
00262         #if email:
00263         #    email = email.strip().lower()
00264         #if not email:
00265         #    email = None
00266         #if title:
00267         #    title = title.strip().lower()
00268         #if not title:
00269         #    title = None
00270 
00271         md_groups = []
00272         uf_groups = []
00273 
00274         if name:
00275             # This will allow us to retrieve groups by their id only
00276             uf_groups = acl_users.searchGroups(id=name)
00277 
00278             # PAS allows search to return dupes. We must winnow...
00279             uf_groups_new = []
00280             for group in uf_groups:
00281                 if group not in uf_groups_new:
00282                     uf_groups_new.append(group)
00283             uf_groups = uf_groups_new
00284 
00285         groups = []
00286         if md_groups or uf_groups:
00287             getGroupById = self.getGroupById
00288 
00289             for groupid in md_groups:
00290                 groups.append(getGroupById(groupid))
00291             for group in uf_groups:
00292                 groupid = group['groupid']
00293                 if groupid in md_groups:
00294                     continue             # Kill dupes
00295                 groups.append(getGroupById(groupid))
00296 
00297             #if not email and \
00298             #       not roles and \
00299             #       not last_login_time:
00300             #    return groups
00301 
00302         return groups
00303 
00304     @deprecate("portal_groups.listGroups is deprecated and will "
00305                "be removed in Plone 3.5. Use PAS searchGroups instead")
00306     def listGroups(self):
00307         # potentially not all groups may be found by this interface
00308         # if the underlying group source doesn't support introspection
00309         groups = []
00310         introspectors = self._getGroupIntrospectors()
00311         for iid, introspector in introspectors:
00312             groups.extend(introspector.getGroups())
00313         return [self.wrapGroup(elt) for elt in groups]
00314 
00315     security.declareProtected(ViewGroups, 'getGroupIds')
00316     @deprecate("portal_groups.getGroupIds is deprecated and will "
00317                "be removed in Plone 3.5. Use PAS searchGroups instead")
00318     def getGroupIds(self):
00319         groups = []
00320         introspectors = self._getGroupIntrospectors()
00321         for iid, introspector in introspectors:
00322             groups.extend(introspector.getGroupIds())
00323         return groups
00324 
00325     listGroupIds = getGroupIds
00326 
00327     security.declareProtected(ViewGroups, 'getGroupMembers')
00328     @deprecate("portal_groups.getGroupMembers is deprecated and will "
00329                "be removed in Plone 3.5. Use PAS to get a group and check "
00330                "its members instead.")
00331     def getGroupMembers(self, group_id):
00332         members = []
00333         introspectors = self._getGroupIntrospectors()
00334         for iid, introspector in introspectors:
00335             members = introspector.getGroupMembers(group_id)
00336             if members:
00337                 break
00338         return members
00339 
00340 
00341     security.declareProtected(ViewGroups, 'getGroupsForPrincipal')
00342     @deprecate("portal_groups.getGroupsForPrincipal is deprecated and will "
00343                "be removed in Plone 3.5. Use PAS to get a principal and check "
00344                "its group list instead.")
00345     def getGroupsForPrincipal(self, principal):
00346         introspectors = self._getGroupIntrospectors()
00347         for iid, introspector in introspectors:
00348             groups = introspector.getGroupsForPrincipal(principal)
00349             if groups:
00350                 break
00351         return groups
00352 
00353     ##
00354     # plugin getters
00355     ##
00356 
00357     security.declarePrivate('_getPlugins')
00358     def _getPlugins(self):
00359         return self.acl_users.plugins
00360 
00361     security.declarePrivate('_getGroupManagers')
00362     def _getGroupManagers(self):
00363         return self._getPlugins().listPlugins(
00364             igroup.IGroupManagement
00365             )
00366 
00367     security.declarePrivate('_getGroupIntrospectors')
00368     def _getGroupIntrospectors(self):
00369         return self._getPlugins().listPlugins(
00370             igroup.IGroupIntrospection
00371             )
00372 
00373     security.declarePrivate('_getGroupSpaceManagers')
00374     def _getGroupSpaceManagers(self):
00375         return self._getPlugins().listPlugins(
00376             igroup.IGroupSpaceManagers
00377             )
00378 
00379 classImplements(GroupsTool,
00380                 *tuple(implementedBy(PloneGroupsTool)) +
00381                 (igroup.IGroupTool,))
00382 
00383 InitializeClass(GroupsTool)
00384 registerToolInterface('portal_groups', igroup.IGroupTool)