eggification
[GroupUserFolder.git] / Products / GroupUserFolder / GroupDataTool.py
diff --git a/Products/GroupUserFolder/GroupDataTool.py b/Products/GroupUserFolder/GroupDataTool.py
new file mode 100644 (file)
index 0000000..fc1bb02
--- /dev/null
@@ -0,0 +1,448 @@
+# -*- coding: utf-8 -*-
+## GroupUserFolder
+## Copyright (C)2006 Ingeniweb
+
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, write to the
+## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+## Copyright (c) 2003 The Connexions Project, All Rights Reserved
+## initially written by J Cameron Cooper, 11 June 2003
+## concept with Brent Hendricks, George Runyan
+"""
+Basic group data tool.
+"""
+__version__ = "$Revision:  $"
+# $Source:  $
+# $Id: GroupDataTool.py 52136 2007-10-21 20:38:00Z encolpe $
+__docformat__ = 'restructuredtext'
+
+from Products.CMFCore.utils import UniqueObject, getToolByName
+from OFS.SimpleItem import SimpleItem
+from OFS.PropertyManager import PropertyManager
+from Globals import DTMLFile
+from Globals import InitializeClass
+from AccessControl.Role import RoleManager
+from BTrees.OOBTree import OOBTree
+from ZPublisher.Converters import type_converters
+from Acquisition import aq_inner, aq_parent, aq_base
+from AccessControl import ClassSecurityInfo, Permissions, Unauthorized, getSecurityManager
+from zope.interface import implements
+from Products.CMFCore.interfaces import IActionProvider
+
+from Products.CMFCore.ActionProviderBase import ActionProviderBase
+
+# BBB CMF < 1.5
+try:
+    from Products.CMFCore.permissions import ManagePortal
+except ImportError:
+    from Products.CMFCore.CMFCorePermissions import ManagePortal
+
+# from Products.CMFCore.MemberDataTool import CleanupTemp
+
+from interfaces.portal_groupdata import portal_groupdata as IGroupDataTool
+from interfaces.portal_groupdata import GroupData as IGroupData
+from Products.GroupUserFolder import postonly
+from Products.GroupUserFolder.GRUFUser import GRUFGroup
+
+_marker = []  # Create a new marker object.
+
+from global_symbols import *
+
+
+class GroupDataTool (UniqueObject, SimpleItem, PropertyManager, ActionProviderBase):
+    """ This tool wraps group objects, allowing transparent access to properties.
+    """
+    # The latter will work only with Plone 1.1 => hence, the if
+    implements(IGroupDataTool, IActionProvider)
+    # __implements__ = (IGroupDataTool, ActionProviderBase.__implements__)
+
+    id = 'portal_groupdata'
+    meta_type = 'CMF Group Data Tool'
+    _actions = ()
+
+    _v_temps = None
+    _properties=({'id':'title', 'type': 'string', 'mode': 'wd'},)
+
+    security = ClassSecurityInfo()
+
+    manage_options=( ActionProviderBase.manage_options +
+                     ({ 'label' : 'Overview'
+                       , 'action' : 'manage_overview'
+                       },
+                     )
+                   + PropertyManager.manage_options
+                   + SimpleItem.manage_options
+                   )
+
+    #
+    #   ZMI methods
+    #
+    security.declareProtected(ManagePortal, 'manage_overview')
+    manage_overview = DTMLFile('dtml/explainGroupDataTool', globals())
+
+    def __init__(self):
+        self._members = OOBTree()
+        # Create the default properties.
+        self._setProperty('description', '', 'text')
+        self._setProperty('email', '', 'string')
+
+    #
+    #   'portal_groupdata' interface methods
+    #
+    security.declarePrivate('wrapGroup')
+    def wrapGroup(self, g):
+        """Returns an object implementing the GroupData interface"""
+        id = g.getId()
+        members = self._members
+        if not members.has_key(id):
+            # Get a temporary member that might be
+            # registered later via registerMemberData().
+            temps = self._v_temps
+            if temps is not None and temps.has_key(id):
+                portal_group = temps[id]
+            else:
+                base = aq_base(self)
+                portal_group = GroupData(base, id)
+                if temps is None:
+                    self._v_temps = {id:portal_group}
+# XXX ClenupTemp doesn't exits
+#                    if hasattr(self, 'REQUEST'):
+#                        # No REQUEST during tests.
+#                        self.REQUEST._hold(CleanupTemp(self))
+                else:
+                    temps[id] = portal_group
+        else:
+            portal_group = members[id]
+        # Return a wrapper with self as containment and
+        # the user as context.
+        return portal_group.__of__(self).__of__(g)
+
+    security.declarePrivate('registerGroupData')
+    def registerGroupData(self, g, id):
+        '''
+        Adds the given member data to the _members dict.
+        This is done as late as possible to avoid side effect
+        transactions and to reduce the necessary number of
+        entries.
+        '''
+        self._members[id] = aq_base(g)
+
+InitializeClass(GroupDataTool)
+
+
+class GroupData (SimpleItem):
+
+    __implements__ = IGroupData
+
+    security = ClassSecurityInfo()
+
+    id = None
+    _tool = None
+
+    def __init__(self, tool, id):
+        self.id = id
+        # Make a temporary reference to the tool.
+        # The reference will be removed by notifyModified().
+        self._tool = tool
+
+    def _getGRUF(self,):
+        return self.acl_users
+
+    security.declarePrivate('notifyModified')
+    def notifyModified(self):
+        # Links self to parent for full persistence.
+        tool = getattr(self, '_tool', None)
+        if tool is not None:
+            del self._tool
+            tool.registerGroupData(self, self.getId())
+
+    security.declarePublic('getGroup')
+    def getGroup(self):
+        """ Returns the actual group implementation. Varies by group
+        implementation (GRUF/Nux/et al). In GRUF this is a user object."""
+        # The user object is our context, but it's possible for
+        # restricted code to strip context while retaining
+        # containment.  Therefore we need a simple security check.
+        parent = aq_parent(self)
+        bcontext = aq_base(parent)
+        bcontainer = aq_base(aq_parent(aq_inner(self)))
+        if bcontext is bcontainer or not hasattr(bcontext, 'getUserName'):
+            raise 'GroupDataError', "Can't find group data"
+        # Return the user object, which is our context.
+        return parent
+
+    def getTool(self):
+        return aq_parent(aq_inner(self))
+
+    security.declarePublic("getGroupMemberIds")
+    def getGroupMemberIds(self,):
+        """
+        Return a list of group member ids
+        """
+        return map(lambda x: x.getMemberId(), self.getGroupMembers())
+
+    security.declarePublic("getAllGroupMemberIds")
+    def getAllGroupMemberIds(self,):
+        """
+        Return a list of group member ids
+        """
+        return map(lambda x: x.getMemberId(), self.getAllGroupMembers())
+
+    security.declarePublic('getGroupMembers')
+    def getGroupMembers(self, ):
+        """
+        Returns a list of the portal_memberdata-ish members of the group.
+        This doesn't include TRANSITIVE groups/users.
+        """
+        md = self.portal_memberdata
+        gd = self.portal_groupdata
+        ret = []
+        for u_name in self.getGroup().getMemberIds(transitive = 0, ):
+            usr = self._getGRUF().getUserById(u_name)
+            if not usr:
+                raise AssertionError, "Cannot retreive a user by its id !"
+            if usr.isGroup():
+                ret.append(gd.wrapGroup(usr))
+            else:
+                ret.append(md.wrapUser(usr))
+        return ret
+
+    security.declarePublic('getAllGroupMembers')
+    def getAllGroupMembers(self, ):
+        """
+        Returns a list of the portal_memberdata-ish members of the group.
+        This will include transitive groups / users
+        """
+        md = self.portal_memberdata
+        gd = self.portal_groupdata
+        ret = []
+        for u_name in self.getGroup().getMemberIds():
+            usr = self._getGRUF().getUserById(u_name)
+            if not usr:
+                raise AssertionError, "Cannot retreive a user by its id !"
+            if usr.isGroup():
+                ret.append(gd.wrapGroup(usr))
+            else:
+                ret.append(md.wrapUser(usr))
+        return ret
+
+    def _getGroup(self,):
+        """
+        _getGroup(self,) => Get the underlying group object
+        """
+        return self._getGRUF().getGroupByName(self.getGroupName())
+
+
+    security.declarePrivate("canAdministrateGroup")
+    def canAdministrateGroup(self,):
+        """
+        Return true if the #current# user can administrate this group
+        """
+        user = getSecurityManager().getUser()
+        tool = self.getTool()
+        portal = getToolByName(tool, 'portal_url').getPortalObject()
+        
+        # Has manager users pemission? 
+        if user.has_permission(Permissions.manage_users, portal):
+            return True
+
+        # Is explicitly mentioned as a group administrator?
+        managers = self.getProperty('delegated_group_member_managers', ())
+        if user.getId() in managers:
+            return True
+
+        # Belongs to a group which is explicitly mentionned as a group administrator
+        meth = getattr(user, "getAllGroupNames", None)
+        if meth:
+            groups = meth()
+        else:
+            groups = ()
+        for v in groups:
+            if v in managers:
+                return True
+
+        # No right to edit this: we complain.
+        return False
+
+    security.declarePublic('addMember')
+    def addMember(self, id, REQUEST=None):
+        """ Add the existing member with the given id to the group"""
+        # We check if the current user can directly or indirectly administrate this group
+        if not self.canAdministrateGroup():
+            raise Unauthorized, "You cannot add a member to the group."
+        self._getGroup().addMember(id)
+
+        # Notify member that they've been changed
+        mtool = getToolByName(self, 'portal_membership')
+        member = mtool.getMemberById(id)
+        if member:
+            member.notifyModified()
+    addMember = postonly(addMember)
+
+    security.declarePublic('removeMember')
+    def removeMember(self, id, REQUEST=None):
+        """Remove the member with the provided id from the group.
+        """
+        # We check if the current user can directly or indirectly administrate this group
+        if not self.canAdministrateGroup():
+            raise Unauthorized, "You cannot remove a member from the group."
+        self._getGroup().removeMember(id)
+
+        # Notify member that they've been changed
+        mtool = getToolByName(self, 'portal_membership')
+        member = mtool.getMemberById(id)
+        if member:
+            member.notifyModified()
+    removeMember = postonly(removeMember)
+
+    security.declareProtected(Permissions.manage_users, 'setProperties')
+    def setProperties(self, properties=None, **kw):
+        '''Allows the manager group to set his/her own properties.
+        Accepts either keyword arguments or a mapping for the "properties"
+        argument.
+        '''
+        if properties is None:
+            properties = kw
+        return self.setGroupProperties(properties)
+
+    security.declareProtected(Permissions.manage_users, 'setGroupProperties')
+    def setGroupProperties(self, mapping):
+        '''Sets the properties of the member.
+        '''
+        # Sets the properties given in the MemberDataTool.
+        tool = self.getTool()
+        for id in tool.propertyIds():
+            if mapping.has_key(id):
+                if not self.__class__.__dict__.has_key(id):
+                    value = mapping[id]
+                    if type(value)==type(''):
+                        proptype = tool.getPropertyType(id) or 'string'
+                        if type_converters.has_key(proptype):
+                            value = type_converters[proptype](value)
+                    setattr(self, id, value)
+                    
+        # Hopefully we can later make notifyModified() implicit.
+        self.notifyModified()
+
+    security.declarePublic('getProperties')
+    def getProperties(self, ):
+        """ Return the properties of this group. Properties are as usual in Zope."""
+        tool = self.getTool()
+        ret = {}
+        for pty in tool.propertyIds():
+            try:
+                ret[pty] = self.getProperty(pty)
+            except ValueError:
+                # We ignore missing ptys
+                continue
+        return ret
+
+    security.declarePublic('getProperty')
+    def getProperty(self, id, default=_marker):
+        """ Returns the value of the property specified by 'id' """
+        tool = self.getTool()
+        base = aq_base( self )
+
+        # First, check the wrapper (w/o acquisition).
+        value = getattr( base, id, _marker )
+        if value is not _marker:
+            return value
+
+        # Then, check the tool and the user object for a value.
+        tool_value = tool.getProperty( id, _marker )
+        user_value = getattr( aq_base(self.getGroup()), id, _marker )
+
+        # If the tool doesn't have the property, use user_value or default
+        if tool_value is _marker:
+            if user_value is not _marker:
+                return user_value
+            elif default is not _marker:
+                return default
+            else:
+                raise ValueError, 'The property %s does not exist' % id
+
+        # If the tool has an empty property and we have a user_value, use it
+        if not tool_value and user_value is not _marker:
+            return user_value
+
+        # Otherwise return the tool value
+        return tool_value
+
+    def __str__(self):
+        return self.getGroupId()
+
+    security.declarePublic("isGroup")
+    def isGroup(self,):
+        """
+        isGroup(self,) => Return true if this is a group.
+        Will always return true for groups.
+        As MemberData objects do not support this method, it is quite useless by now.
+        So one can use groupstool.isGroup(g) instead to get this information.
+        """
+        return 1
+
+    ### Group object interface ###
+
+    security.declarePublic('getGroupName')
+    def getGroupName(self):
+        """Return the name of the group, without any special decorations (like GRUF prefixes.)"""
+        return self.getGroup().getName()
+
+    security.declarePublic('getGroupId')
+    def getGroupId(self):
+        """Get the ID of the group. The ID can be used, at least from
+        Python, to get the user from the user's UserDatabase.
+        Within Plone, all group ids are UNPREFIXED."""
+        if isinstance(self, GRUFGroup):
+            return self.getGroup().getId(unprefixed = 1)
+        else:
+            return self.getGroup().getId()
+
+    def getGroupTitleOrName(self):
+        """Get the Title property of the group. If there is none
+        then return the name """
+        title = self.getProperty('title', None)
+        return title or self.getGroupName()
+
+    security.declarePublic("getMemberId")
+    def getMemberId(self,):
+        """This exists only for a basic user/group API compatibility
+        """
+        return self.getGroupId()
+
+    security.declarePublic('getRoles')
+    def getRoles(self):
+        """Return the list of roles assigned to a user."""
+        return self.getGroup().getRoles()
+
+    security.declarePublic('getRolesInContext')
+    def getRolesInContext(self, object):
+        """Return the list of roles assigned to the user,  including local
+        roles assigned in context of the passed in object."""
+        return self.getGroup().getRolesInContext(object)
+
+    security.declarePublic('getDomains')
+    def getDomains(self):
+        """Return the list of domain restrictions for a user"""
+        return self.getGroup().getDomains()
+
+    security.declarePublic('has_role')
+    def has_role(self, roles, object=None):
+        """Check to see if a user has a given role or roles."""
+        return self.getGroup().has_role(roles, object)
+
+    # There are other parts of the interface but they are
+    # deprecated for use with CMF applications.
+
+InitializeClass(GroupData)