1 # -*- coding: utf-8 -*-
3 ## Copyright (C)2006 Ingeniweb
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 2 of the License, or
8 ## (at your option) any later version.
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program; see the file COPYING. If not, write to the
17 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 ## Copyright (c) 2003 The Connexions Project, All Rights Reserved
20 ## initially written by J Cameron Cooper, 11 June 2003
21 ## concept with Brent Hendricks, George Runyan
23 Basic group data tool.
25 __version__
= "$Revision: $"
27 # $Id: GroupDataTool.py 52136 2007-10-21 20:38:00Z encolpe $
28 __docformat__
= 'restructuredtext'
30 from Products
.CMFCore
.utils
import UniqueObject
, getToolByName
31 from OFS
.SimpleItem
import SimpleItem
32 from OFS
.PropertyManager
import PropertyManager
33 from Globals
import DTMLFile
34 from Globals
import InitializeClass
35 from AccessControl
.Role
import RoleManager
36 from BTrees
.OOBTree
import OOBTree
37 from ZPublisher
.Converters
import type_converters
38 from Acquisition
import aq_inner
, aq_parent
, aq_base
39 from AccessControl
import ClassSecurityInfo
, Permissions
, Unauthorized
, getSecurityManager
40 from zope
.interface
import implements
41 from Products
.CMFCore
.interfaces
import IActionProvider
43 from Products
.CMFCore
.ActionProviderBase
import ActionProviderBase
47 from Products
.CMFCore
.permissions
import ManagePortal
49 from Products
.CMFCore
.CMFCorePermissions
import ManagePortal
51 # from Products.CMFCore.MemberDataTool import CleanupTemp
53 from interfaces
.portal_groupdata
import portal_groupdata
as IGroupDataTool
54 from interfaces
.portal_groupdata
import GroupData
as IGroupData
55 from Products
.GroupUserFolder
import postonly
56 from Products
.GroupUserFolder
.GRUFUser
import GRUFGroup
58 _marker
= [] # Create a new marker object.
60 from global_symbols
import *
63 class GroupDataTool (UniqueObject
, SimpleItem
, PropertyManager
, ActionProviderBase
):
64 """ This tool wraps group objects, allowing transparent access to properties.
66 # The latter will work only with Plone 1.1 => hence, the if
67 implements(IGroupDataTool
, IActionProvider
)
68 # __implements__ = (IGroupDataTool, ActionProviderBase.__implements__)
70 id = 'portal_groupdata'
71 meta_type
= 'CMF Group Data Tool'
75 _properties
=({'id':'title', 'type': 'string', 'mode': 'wd'},)
77 security
= ClassSecurityInfo()
79 manage_options
=( ActionProviderBase
.manage_options
+
80 ({ 'label' : 'Overview'
81 , 'action' : 'manage_overview'
84 + PropertyManager
.manage_options
85 + SimpleItem
.manage_options
91 security
.declareProtected(ManagePortal
, 'manage_overview')
92 manage_overview
= DTMLFile('dtml/explainGroupDataTool', globals())
95 self
._members
= OOBTree()
96 # Create the default properties.
97 self
._setProperty
('description', '', 'text')
98 self
._setProperty
('email', '', 'string')
101 # 'portal_groupdata' interface methods
103 security
.declarePrivate('wrapGroup')
104 def wrapGroup(self
, g
):
105 """Returns an object implementing the GroupData interface"""
107 members
= self
._members
108 if not members
.has_key(id):
109 # Get a temporary member that might be
110 # registered later via registerMemberData().
111 temps
= self
._v
_temps
112 if temps
is not None and temps
.has_key(id):
113 portal_group
= temps
[id]
116 portal_group
= GroupData(base
, id)
118 self
._v
_temps
= {id:portal_group
}
119 # XXX ClenupTemp doesn't exits
120 # if hasattr(self, 'REQUEST'):
121 # # No REQUEST during tests.
122 # self.REQUEST._hold(CleanupTemp(self))
124 temps
[id] = portal_group
126 portal_group
= members
[id]
127 # Return a wrapper with self as containment and
128 # the user as context.
129 return portal_group
.__of
__(self
).__of
__(g
)
131 security
.declarePrivate('registerGroupData')
132 def registerGroupData(self
, g
, id):
134 Adds the given member data to the _members dict.
135 This is done as late as possible to avoid side effect
136 transactions and to reduce the necessary number of
139 self
._members
[id] = aq_base(g
)
141 InitializeClass(GroupDataTool
)
144 class GroupData (SimpleItem
):
146 __implements__
= IGroupData
148 security
= ClassSecurityInfo()
153 def __init__(self
, tool
, id):
155 # Make a temporary reference to the tool.
156 # The reference will be removed by notifyModified().
160 return self
.acl_users
162 security
.declarePrivate('notifyModified')
163 def notifyModified(self
):
164 # Links self to parent for full persistence.
165 tool
= getattr(self
, '_tool', None)
168 tool
.registerGroupData(self
, self
.getId())
170 security
.declarePublic('getGroup')
172 """ Returns the actual group implementation. Varies by group
173 implementation (GRUF/Nux/et al). In GRUF this is a user object."""
174 # The user object is our context, but it's possible for
175 # restricted code to strip context while retaining
176 # containment. Therefore we need a simple security check.
177 parent
= aq_parent(self
)
178 bcontext
= aq_base(parent
)
179 bcontainer
= aq_base(aq_parent(aq_inner(self
)))
180 if bcontext
is bcontainer
or not hasattr(bcontext
, 'getUserName'):
181 raise 'GroupDataError', "Can't find group data"
182 # Return the user object, which is our context.
186 return aq_parent(aq_inner(self
))
188 security
.declarePublic("getGroupMemberIds")
189 def getGroupMemberIds(self
,):
191 Return a list of group member ids
193 return map(lambda x
: x
.getMemberId(), self
.getGroupMembers())
195 security
.declarePublic("getAllGroupMemberIds")
196 def getAllGroupMemberIds(self
,):
198 Return a list of group member ids
200 return map(lambda x
: x
.getMemberId(), self
.getAllGroupMembers())
202 security
.declarePublic('getGroupMembers')
203 def getGroupMembers(self
, ):
205 Returns a list of the portal_memberdata-ish members of the group.
206 This doesn't include TRANSITIVE groups/users.
208 md
= self
.portal_memberdata
209 gd
= self
.portal_groupdata
211 for u_name
in self
.getGroup().getMemberIds(transitive
= 0, ):
212 usr
= self
._getGRUF
().getUserById(u_name
)
214 raise AssertionError, "Cannot retreive a user by its id !"
216 ret
.append(gd
.wrapGroup(usr
))
218 ret
.append(md
.wrapUser(usr
))
221 security
.declarePublic('getAllGroupMembers')
222 def getAllGroupMembers(self
, ):
224 Returns a list of the portal_memberdata-ish members of the group.
225 This will include transitive groups / users
227 md
= self
.portal_memberdata
228 gd
= self
.portal_groupdata
230 for u_name
in self
.getGroup().getMemberIds():
231 usr
= self
._getGRUF
().getUserById(u_name
)
233 raise AssertionError, "Cannot retreive a user by its id !"
235 ret
.append(gd
.wrapGroup(usr
))
237 ret
.append(md
.wrapUser(usr
))
240 def _getGroup(self
,):
242 _getGroup(self,) => Get the underlying group object
244 return self
._getGRUF
().getGroupByName(self
.getGroupName())
247 security
.declarePrivate("canAdministrateGroup")
248 def canAdministrateGroup(self
,):
250 Return true if the #current# user can administrate this group
252 user
= getSecurityManager().getUser()
253 tool
= self
.getTool()
254 portal
= getToolByName(tool
, 'portal_url').getPortalObject()
256 # Has manager users pemission?
257 if user
.has_permission(Permissions
.manage_users
, portal
):
260 # Is explicitly mentioned as a group administrator?
261 managers
= self
.getProperty('delegated_group_member_managers', ())
262 if user
.getId() in managers
:
265 # Belongs to a group which is explicitly mentionned as a group administrator
266 meth
= getattr(user
, "getAllGroupNames", None)
275 # No right to edit this: we complain.
278 security
.declarePublic('addMember')
279 def addMember(self
, id, REQUEST
=None):
280 """ Add the existing member with the given id to the group"""
281 # We check if the current user can directly or indirectly administrate this group
282 if not self
.canAdministrateGroup():
283 raise Unauthorized
, "You cannot add a member to the group."
284 self
._getGroup
().addMember(id)
286 # Notify member that they've been changed
287 mtool
= getToolByName(self
, 'portal_membership')
288 member
= mtool
.getMemberById(id)
290 member
.notifyModified()
291 addMember
= postonly(addMember
)
293 security
.declarePublic('removeMember')
294 def removeMember(self
, id, REQUEST
=None):
295 """Remove the member with the provided id from the group.
297 # We check if the current user can directly or indirectly administrate this group
298 if not self
.canAdministrateGroup():
299 raise Unauthorized
, "You cannot remove a member from the group."
300 self
._getGroup
().removeMember(id)
302 # Notify member that they've been changed
303 mtool
= getToolByName(self
, 'portal_membership')
304 member
= mtool
.getMemberById(id)
306 member
.notifyModified()
307 removeMember
= postonly(removeMember
)
309 security
.declareProtected(Permissions
.manage_users
, 'setProperties')
310 def setProperties(self
, properties
=None, **kw
):
311 '''Allows the manager group to set his/her own properties.
312 Accepts either keyword arguments or a mapping for the "properties"
315 if properties
is None:
317 return self
.setGroupProperties(properties
)
319 security
.declareProtected(Permissions
.manage_users
, 'setGroupProperties')
320 def setGroupProperties(self
, mapping
):
321 '''Sets the properties of the member.
323 # Sets the properties given in the MemberDataTool.
324 tool
= self
.getTool()
325 for id in tool
.propertyIds():
326 if mapping
.has_key(id):
327 if not self
.__class
__.__dict
__.has_key(id):
329 if type(value
)==type(''):
330 proptype
= tool
.getPropertyType(id) or 'string'
331 if type_converters
.has_key(proptype
):
332 value
= type_converters
[proptype
](value
)
333 setattr(self
, id, value
)
335 # Hopefully we can later make notifyModified() implicit.
336 self
.notifyModified()
338 security
.declarePublic('getProperties')
339 def getProperties(self
, ):
340 """ Return the properties of this group. Properties are as usual in Zope."""
341 tool
= self
.getTool()
343 for pty
in tool
.propertyIds():
345 ret
[pty
] = self
.getProperty(pty
)
347 # We ignore missing ptys
351 security
.declarePublic('getProperty')
352 def getProperty(self
, id, default
=_marker
):
353 """ Returns the value of the property specified by 'id' """
354 tool
= self
.getTool()
355 base
= aq_base( self
)
357 # First, check the wrapper (w/o acquisition).
358 value
= getattr( base
, id, _marker
)
359 if value
is not _marker
:
362 # Then, check the tool and the user object for a value.
363 tool_value
= tool
.getProperty( id, _marker
)
364 user_value
= getattr( aq_base(self
.getGroup()), id, _marker
)
366 # If the tool doesn't have the property, use user_value or default
367 if tool_value
is _marker
:
368 if user_value
is not _marker
:
370 elif default
is not _marker
:
373 raise ValueError, 'The property %s does not exist' % id
375 # If the tool has an empty property and we have a user_value, use it
376 if not tool_value
and user_value
is not _marker
:
379 # Otherwise return the tool value
383 return self
.getGroupId()
385 security
.declarePublic("isGroup")
388 isGroup(self,) => Return true if this is a group.
389 Will always return true for groups.
390 As MemberData objects do not support this method, it is quite useless by now.
391 So one can use groupstool.isGroup(g) instead to get this information.
395 ### Group object interface ###
397 security
.declarePublic('getGroupName')
398 def getGroupName(self
):
399 """Return the name of the group, without any special decorations (like GRUF prefixes.)"""
400 return self
.getGroup().getName()
402 security
.declarePublic('getGroupId')
403 def getGroupId(self
):
404 """Get the ID of the group. The ID can be used, at least from
405 Python, to get the user from the user's UserDatabase.
406 Within Plone, all group ids are UNPREFIXED."""
407 if isinstance(self
, GRUFGroup
):
408 return self
.getGroup().getId(unprefixed
= 1)
410 return self
.getGroup().getId()
412 def getGroupTitleOrName(self
):
413 """Get the Title property of the group. If there is none
414 then return the name """
415 title
= self
.getProperty('title', None)
416 return title
or self
.getGroupName()
418 security
.declarePublic("getMemberId")
419 def getMemberId(self
,):
420 """This exists only for a basic user/group API compatibility
422 return self
.getGroupId()
424 security
.declarePublic('getRoles')
426 """Return the list of roles assigned to a user."""
427 return self
.getGroup().getRoles()
429 security
.declarePublic('getRolesInContext')
430 def getRolesInContext(self
, object):
431 """Return the list of roles assigned to the user, including local
432 roles assigned in context of the passed in object."""
433 return self
.getGroup().getRolesInContext(object)
435 security
.declarePublic('getDomains')
436 def getDomains(self
):
437 """Return the list of domain restrictions for a user"""
438 return self
.getGroup().getDomains()
440 security
.declarePublic('has_role')
441 def has_role(self
, roles
, object=None):
442 """Check to see if a user has a given role or roles."""
443 return self
.getGroup().has_role(roles
, object)
445 # There are other parts of the interface but they are
446 # deprecated for use with CMF applications.
448 InitializeClass(GroupData
)