Back to index

plone3  3.1.7
DynamicGroupsPlugin.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights
00004 # Reserved.
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 """ Classes: DynamicGroupsPlugin
00016 
00017 $Id: DynamicGroupsPlugin.py 73968 2007-04-01 20:13:59Z alecm $
00018 """
00019 import copy
00020 
00021 from Acquisition import aq_inner, aq_parent
00022 from AccessControl import ClassSecurityInfo
00023 from OFS.SimpleItem import SimpleItem
00024 from OFS.PropertyManager import PropertyManager
00025 from OFS.Folder import Folder
00026 from OFS.Cache import Cacheable
00027 from Globals import InitializeClass
00028 from Persistence import PersistentMapping
00029 
00030 from zope.interface import Interface
00031 
00032 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00033 from Products.PageTemplates.Expressions import getEngine
00034 
00035 from Products.PluggableAuthService.interfaces.plugins \
00036     import IGroupsPlugin
00037 from Products.PluggableAuthService.interfaces.plugins \
00038     import IGroupEnumerationPlugin
00039 from Products.PluggableAuthService.permissions import ManageGroups
00040 from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
00041 from Products.PluggableAuthService.utils import createViewName
00042 from Products.PluggableAuthService.utils import classImplements
00043 from Products.PluggableAuthService.utils import postonly
00044 
00045 class IDynamicGroupsPlugin(Interface):
00046     """ Marker interface.
00047     """
00048 
00049 
00050 manage_addDynamicGroupsPluginForm = PageTemplateFile(
00051         'www/dgpAdd', globals(), __name__= 'manage_addDynamicGroupsPluginForm' )
00052 
00053 def addDynamicGroupsPlugin( dispatcher, id, title='', RESPONSE=None ):
00054 
00055     """ Add a DGP to 'dispatcher'.
00056     """
00057     dgp = DynamicGroupsPlugin( id, title )
00058     dispatcher._setObject( id, dgp )
00059 
00060     if RESPONSE is not None:
00061         RESPONSE.redirect( '%s/manage_main?manage_tabs_messsage=%s'
00062                          % ( dispatcher.absolute_url()
00063                            , 'DPG+added.'
00064                            )
00065                          )
00066 
00067 class DynamicGroupDefinition( SimpleItem, PropertyManager ):
00068 
00069     """ Represent a single dynamic group.
00070     """
00071     meta_type = 'Dynamic Group Definition'
00072     security = ClassSecurityInfo()
00073     security.declareObjectProtected( ManageGroups )
00074 
00075     _v_compiled = None
00076 
00077 
00078     _properties = ( { 'id' : 'id'
00079                     , 'type' : 'string'
00080                     , 'mode' : ''
00081                     }
00082                   , { 'id' : 'predicate'
00083                     , 'type' : 'string'
00084                     , 'mode' : 'w'
00085                     }
00086                   , { 'id' : 'title'
00087                     , 'type' : 'string'
00088                     , 'mode' : 'w'
00089                     }
00090                   , { 'id' : 'description'
00091                     , 'type' : 'text'
00092                     , 'mode' : 'w'
00093                     }
00094                   , { 'id' : 'active'
00095                     , 'type' : 'boolean'
00096                     , 'mode' : 'w'
00097                     }
00098                   )
00099 
00100     def __init__( self, id, predicate, title, description, active ):
00101 
00102         self._setId( id )
00103         self._setPredicate( predicate )
00104 
00105         self.title = title
00106         self.description = description
00107         self.active = bool( active )
00108 
00109     def __call__( self, principal, request=None ):
00110 
00111         """ Evaluate our expression to determine whether 'principal' belongs.
00112         """
00113         predicate = self._getPredicate()
00114         plugin = aq_parent( aq_inner( self ) )
00115         properties = {}
00116 
00117         for k, v in self.propertyItems():
00118             properties[ k ] = v
00119 
00120         data = getEngine().getContext( { 'request' :    request
00121                                        , 'nothing' :    None
00122                                        , 'principal' :  principal
00123                                        , 'group' :      properties
00124                                        , 'plugin' :     plugin
00125                                        } )
00126 
00127         result = predicate( data )
00128 
00129         if isinstance( result, Exception ):
00130             raise result
00131 
00132         return result
00133 
00134     security.declarePrivate( '_setPredicate' )
00135     def _setPredicate( self, predicate ):
00136 
00137         self.predicate = predicate
00138 
00139         if self._v_compiled is not None:
00140             del self._v_compiled
00141 
00142     security.declarePrivate( '_getPredicate' )
00143     def _getPredicate( self ):
00144 
00145         if self._v_compiled is None:
00146             self._v_compiled = getEngine().compile( self.predicate )
00147 
00148         return self._v_compiled
00149 
00150     security.declarePrivate( '_updateProperty' )
00151     def _updateProperty( self, id, value ):
00152 
00153         if id == 'predicate':
00154             self._setPredicate( value )
00155 
00156         else:
00157             PropertyManager._updateProperty( self, id, value )
00158 
00159     #
00160     #   ZMI
00161     #
00162     manage_options = ( PropertyManager.manage_options
00163                      + SimpleItem.manage_options
00164                      )
00165 
00166 InitializeClass( DynamicGroupDefinition )
00167 
00168 
00169 class DynamicGroupsPlugin( Folder, BasePlugin, Cacheable ):
00170 
00171     """ Define groups via business rules.
00172 
00173     o Membership in a candidate group is established via a predicate,
00174       expressed as a TALES expression.  Names available to the predicate
00175       include:
00176 
00177       'group' -- the dynamic group definition object itself
00178 
00179       'plugin' -- this plugin object
00180 
00181       'principal' -- the principal being tested.
00182 
00183       'request' -- the request object.
00184     """
00185     meta_type = 'Dynamic Groups Plugin'
00186 
00187     security = ClassSecurityInfo()
00188 
00189     def __init__( self, id, title='' ):
00190 
00191         self._setId( id )
00192         self.title = title
00193 
00194     #
00195     #   Plugin implementations
00196     #
00197     security.declareProtected( ManageGroups, 'getGroupsForPrincipal' )
00198     def getGroupsForPrincipal( self, principal, request=None ):
00199 
00200         """ See IGroupsPlugin.
00201         """
00202         grps = []
00203         DGD = DynamicGroupDefinition.meta_type
00204         for group in self.objectValues( DGD ):
00205             if group.active and group( principal, request ):
00206                 grps.append('%s%s' % (self.prefix, group.getId()))
00207         return grps
00208 
00209     security.declareProtected( ManageGroups, 'enumerateGroups' )
00210     def enumerateGroups( self
00211                        , id=None
00212                        , exact_match=False
00213                        , sort_by=None
00214                        , max_results=None
00215                        , **kw
00216                        ):
00217         """ See IGroupEnumerationPlugin.
00218         """
00219         group_info = []
00220         group_ids = []
00221         plugin_id = self.getId()
00222         view_name = createViewName('enumerateGroups', id)
00223 
00224         # Look in the cache first...
00225         keywords = copy.deepcopy(kw)
00226         keywords.update( { 'id' : id
00227                          , 'exact_match' : exact_match
00228                          , 'sort_by' : sort_by
00229                          , 'max_results' : max_results
00230                          }
00231                        )
00232         cached_info = self.ZCacheable_get( view_name=view_name
00233                                          , keywords=keywords
00234                                          , default=None
00235                                          )
00236 
00237         if cached_info is not None:
00238             return tuple(cached_info)
00239 
00240         if isinstance( id, str ):
00241             id = [ id ]
00242 
00243         if exact_match and id:
00244             group_ids.extend( id )
00245 
00246         if group_ids:
00247             group_filter = None
00248 
00249         else:   # Searching
00250             group_ids = self.listGroupIds()
00251             group_filter = _DynamicGroupFilter( id, **kw )
00252 
00253         for group_id in group_ids:
00254 
00255             url = '/%s/%s/manage_propertiesForm' % ( self.absolute_url( 1 )
00256                                                    , group_id )
00257             info = {}
00258             info.update( self.getGroupInfo( group_id ) )
00259 
00260             info[ 'pluginid' ] = plugin_id
00261             info[ 'properties_url' ] = url
00262             info[ 'members_url' ] = url
00263 
00264             info[ 'id' ] = '%s%s' % (self.prefix, info['id'])
00265 
00266             if not group_filter or group_filter( info ):
00267                 if info[ 'active' ]:
00268                     group_info.append( info )
00269 
00270         # Put the computed value into the cache
00271         self.ZCacheable_set(group_info, view_name=view_name, keywords=keywords)
00272 
00273         return tuple( group_info )
00274 
00275     #
00276     #   Housekeeping
00277     #
00278     security.declareProtected( ManageGroups, 'listGroupIds' )
00279     def listGroupIds( self ):
00280 
00281         """ Return a list of IDs for the dynamic groups we manage.
00282         """
00283         return self.objectIds( DynamicGroupDefinition.meta_type )
00284 
00285     security.declareProtected( ManageGroups, 'getGroupInfo' )
00286     def getGroupInfo( self, group_id ):
00287 
00288         """ Return a mappings describing one dynamic group we manage.
00289 
00290         o Raise KeyError if we don't have an existing group definition
00291           for 'group_ id'.
00292 
00293         o Keys include:
00294 
00295           'id' -- the group's ID
00296 
00297           'predicate' -- the TALES expression defining group membership
00298 
00299           'active' -- boolean flag:  is the group currently active?
00300         """
00301         try:
00302             original = self._getOb( group_id )
00303         except AttributeError:
00304             try:
00305                 original = self._getOb( group_id[len(self.prefix):] )
00306             except AttributeError:
00307                 raise KeyError, group_id
00308 
00309         if not isinstance( original, DynamicGroupDefinition ):
00310             raise KeyError, group_id
00311 
00312         info = {}
00313 
00314         for k, v in original.propertyItems():
00315             info[ k ] = v
00316 
00317         return info
00318 
00319     security.declareProtected( ManageGroups, 'listGroupInfo' )
00320     def listGroupInfo( self ):
00321 
00322         """ Return a list of mappings describing the dynamic groups we manage.
00323 
00324         o Keys include:
00325 
00326           'id' -- the group's ID
00327 
00328           'predicate' -- the TALES expression defining group membership
00329 
00330           'active' -- boolean flag:  is the group currently active?
00331         """
00332         return [ self.getGroupInfo( x ) for x in self.listGroupIds() ]
00333 
00334     security.declareProtected( ManageGroups, 'addGroup' )
00335     def addGroup( self
00336                 , group_id
00337                 , predicate
00338                 , title=''
00339                 , description=''
00340                 , active=True
00341                 ):
00342 
00343         """ Add a group definition.
00344 
00345         o Raise KeyError if we have an existing group definition
00346           for 'group_id'.
00347         """
00348         if group_id in self.listGroupIds():
00349             raise KeyError, 'Duplicate group ID: %s' % group_id
00350 
00351         info = DynamicGroupDefinition( group_id
00352                                      , predicate
00353                                      , title
00354                                      , description
00355                                      , active
00356                                      )
00357 
00358         self._setObject( group_id, info )
00359 
00360         # This method changes the enumerateGroups return value
00361         view_name = createViewName('enumerateGroups')
00362         self.ZCacheable_invalidate(view_name=view_name)
00363             
00364     security.declareProtected( ManageGroups, 'updateGroup' )
00365     def updateGroup( self
00366                    , group_id
00367                    , predicate
00368                    , title=None
00369                    , description=None
00370                    , active=None
00371                    ):
00372 
00373         """ Update a group definition.
00374 
00375         o Raise KeyError if we don't have an existing group definition
00376           for 'group_id'.
00377 
00378         o Don't update 'title', 'description', or 'active' unless supplied.
00379         """
00380         if group_id not in self.listGroupIds():
00381             raise KeyError, 'Invalid group ID: %s' % group_id
00382 
00383         group = self._getOb( group_id )
00384 
00385         group._setPredicate( predicate )
00386 
00387         if title is not None:
00388             group.title = title
00389 
00390         if description is not None:
00391             group.description = description
00392 
00393         if active is not None:
00394             group.active = active
00395 
00396         # This method changes the enumerateGroups return value
00397         view_name = createViewName('enumerateGroups')
00398         self.ZCacheable_invalidate(view_name=view_name)
00399         view_name = createViewName('enumerateGroups', group_id)
00400         self.ZCacheable_invalidate(view_name=view_name)
00401             
00402     security.declareProtected( ManageGroups, 'removeGroup' )
00403     def removeGroup( self, group_id, REQUEST=None ):
00404 
00405         """ Remove a group definition.
00406 
00407         o Raise KeyError if we don't have an existing group definition
00408           for 'group_id'.
00409         """
00410         if group_id not in self.listGroupIds():
00411             raise KeyError, 'Invalid group ID: %s' % group_id
00412 
00413         self._delObject( group_id )
00414 
00415         # This method changes the enumerateGroups return value
00416         view_name = createViewName('enumerateGroups')
00417         self.ZCacheable_invalidate(view_name=view_name)
00418         view_name = createViewName('enumerateGroups', group_id)
00419         self.ZCacheable_invalidate(view_name=view_name)
00420     removeGroup = postonly(removeGroup)
00421 
00422     #
00423     #   ZMI
00424     #
00425     manage_options = ( ( { 'label' : 'Groups'
00426                          , 'action' : 'manage_groups'
00427                          }
00428                        ,
00429                        )
00430                      + Folder.manage_options[:1]
00431                      + BasePlugin.manage_options[:1]
00432                      + Folder.manage_options[1:]
00433                      + Cacheable.manage_options
00434                      )
00435 
00436     manage_groups = PageTemplateFile( 'www/dgpGroups'
00437                                     , globals()
00438                                     , __name__='manage_groups'
00439                                     )
00440 
00441     security.declareProtected( ManageGroups, 'manage_addGroup' )
00442     def manage_addGroup( self
00443                        , group_id
00444                        , title
00445                        , description
00446                        , predicate
00447                        , active=True
00448                        , RESPONSE=None
00449                        ):
00450         """ Add a group via the ZMI.
00451         """
00452         self.addGroup( group_id
00453                      , predicate
00454                      , title
00455                      , description
00456                      , active
00457                      )
00458 
00459         message = 'Group+%s+added' % group_id
00460 
00461         if RESPONSE is not None:
00462             RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
00463                             % ( self.absolute_url(), message )
00464                             )
00465 
00466     security.declareProtected( ManageGroups, 'manage_updateGroup' )
00467     def manage_updateGroup( self
00468                           , group_id
00469                           , predicate
00470                           , title=None
00471                           , description=None
00472                           , active=True
00473                           , RESPONSE=None
00474                           ):
00475         """ Update a group via the ZMI.
00476         """
00477         self.updateGroup( group_id
00478                         , predicate
00479                         , title
00480                         , description
00481                         , active
00482                         )
00483 
00484         message = 'Group+%s+updated' % group_id
00485 
00486         if RESPONSE is not None:
00487             RESPONSE.redirect( ( '%s/manage_groups?group_id=%s&'
00488                                + 'manage_tabs_message=%s'
00489                                ) % ( self.absolute_url(), group_id, message )
00490                              )
00491 
00492     security.declareProtected( ManageGroups, 'manage_removeGroups' )
00493     def manage_removeGroups( self
00494                            , group_ids
00495                            , RESPONSE=None
00496                            , REQUEST=None
00497                            ):
00498         """ Remove one or more groups via the ZMI.
00499         """
00500         group_ids = filter( None, group_ids )
00501 
00502         if not group_ids:
00503             message = 'no+groups+selected'
00504 
00505         else:
00506         
00507             for group_id in group_ids:
00508                 self.removeGroup( group_id )
00509 
00510             message = 'Groups+removed'
00511 
00512         if RESPONSE is not None:
00513             RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
00514                              % ( self.absolute_url(), message )
00515                              )
00516     manage_removeGroups = postonly(manage_removeGroups)
00517 
00518 classImplements( DynamicGroupsPlugin
00519                , IDynamicGroupsPlugin
00520                , IGroupsPlugin
00521                , IGroupEnumerationPlugin
00522                )
00523 
00524 InitializeClass( DynamicGroupsPlugin )
00525 
00526 class _DynamicGroupFilter:
00527 
00528     def __init__( self
00529                 , id=None
00530                 , **kw
00531                 ):
00532 
00533         self._filter_ids = id
00534 
00535     def __call__( self, group_info ):
00536 
00537         if self._filter_ids:
00538 
00539             key = 'id'
00540 
00541         else:
00542             return 1 # TODO:  try using 'kw'
00543 
00544         value = group_info.get( key )
00545 
00546         if not value:
00547             return 0
00548 
00549         for id in self._filter_ids:
00550             if value.find( id ) >= 0:
00551                 return 1
00552 
00553         return 0