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
41 from Products
.CMFCore
.ActionProviderBase
import ActionProviderBase
44 from Products
.CMFCore
.permissions
import ManagePortal
46 from Products
.CMFCore
.CMFCorePermissions
import ManagePortal
48 from Products
.CMFCore
.MemberDataTool
import CleanupTemp
50 from interfaces
.portal_groupdata
import portal_groupdata
as IGroupDataTool
51 from interfaces
.portal_groupdata
import GroupData
as IGroupData
52 from Products
.GroupUserFolder
import postonly
53 from Products
.GroupUserFolder
.GRUFUser
import GRUFGroup
55 _marker
= [] # Create a new marker object.
57 from global_symbols
import *
60 class GroupDataTool (UniqueObject
, SimpleItem
, PropertyManager
, ActionProviderBase
):
61 """ This tool wraps group objects, allowing transparent access to properties.
63 # The latter will work only with Plone 1.1 => hence, the if
64 __implements__
= (IGroupDataTool
, ActionProviderBase
.__implements
__)
66 id = 'portal_groupdata'
67 meta_type
= 'CMF Group Data Tool'
71 _properties
=({'id':'title', 'type': 'string', 'mode': 'wd'},)
73 security
= ClassSecurityInfo()
75 manage_options
=( ActionProviderBase
.manage_options
+
76 ({ 'label' : 'Overview'
77 , 'action' : 'manage_overview'
80 + PropertyManager
.manage_options
81 + SimpleItem
.manage_options
87 security
.declareProtected(ManagePortal
, 'manage_overview')
88 manage_overview
= DTMLFile('dtml/explainGroupDataTool', globals())
91 self
._members
= OOBTree()
92 # Create the default properties.
93 self
._setProperty
('description', '', 'text')
94 self
._setProperty
('email', '', 'string')
97 # 'portal_groupdata' interface methods
99 security
.declarePrivate('wrapGroup')
100 def wrapGroup(self
, g
):
101 """Returns an object implementing the GroupData interface"""
103 members
= self
._members
104 if not members
.has_key(id):
105 # Get a temporary member that might be
106 # registered later via registerMemberData().
107 temps
= self
._v
_temps
108 if temps
is not None and temps
.has_key(id):
109 portal_group
= temps
[id]
112 portal_group
= GroupData(base
, id)
114 self
._v
_temps
= {id:portal_group
}
115 if hasattr(self
, 'REQUEST'):
116 # No REQUEST during tests.
117 self
.REQUEST
._hold
(CleanupTemp(self
))
119 temps
[id] = portal_group
121 portal_group
= members
[id]
122 # Return a wrapper with self as containment and
123 # the user as context.
124 return portal_group
.__of
__(self
).__of
__(g
)
126 security
.declarePrivate('registerGroupData')
127 def registerGroupData(self
, g
, id):
129 Adds the given member data to the _members dict.
130 This is done as late as possible to avoid side effect
131 transactions and to reduce the necessary number of
134 self
._members
[id] = aq_base(g
)
136 InitializeClass(GroupDataTool
)
139 class GroupData (SimpleItem
):
141 __implements__
= IGroupData
143 security
= ClassSecurityInfo()
148 def __init__(self
, tool
, id):
150 # Make a temporary reference to the tool.
151 # The reference will be removed by notifyModified().
155 return self
.acl_users
157 security
.declarePrivate('notifyModified')
158 def notifyModified(self
):
159 # Links self to parent for full persistence.
160 tool
= getattr(self
, '_tool', None)
163 tool
.registerGroupData(self
, self
.getId())
165 security
.declarePublic('getGroup')
167 """ Returns the actual group implementation. Varies by group
168 implementation (GRUF/Nux/et al). In GRUF this is a user object."""
169 # The user object is our context, but it's possible for
170 # restricted code to strip context while retaining
171 # containment. Therefore we need a simple security check.
172 parent
= aq_parent(self
)
173 bcontext
= aq_base(parent
)
174 bcontainer
= aq_base(aq_parent(aq_inner(self
)))
175 if bcontext
is bcontainer
or not hasattr(bcontext
, 'getUserName'):
176 raise 'GroupDataError', "Can't find group data"
177 # Return the user object, which is our context.
181 return aq_parent(aq_inner(self
))
183 security
.declarePublic("getGroupMemberIds")
184 def getGroupMemberIds(self
,):
186 Return a list of group member ids
188 return map(lambda x
: x
.getMemberId(), self
.getGroupMembers())
190 security
.declarePublic("getAllGroupMemberIds")
191 def getAllGroupMemberIds(self
,):
193 Return a list of group member ids
195 return map(lambda x
: x
.getMemberId(), self
.getAllGroupMembers())
197 security
.declarePublic('getGroupMembers')
198 def getGroupMembers(self
, ):
200 Returns a list of the portal_memberdata-ish members of the group.
201 This doesn't include TRANSITIVE groups/users.
203 md
= self
.portal_memberdata
204 gd
= self
.portal_groupdata
206 for u_name
in self
.getGroup().getMemberIds(transitive
= 0, ):
207 usr
= self
._getGRUF
().getUserById(u_name
)
209 raise AssertionError, "Cannot retreive a user by its id !"
211 ret
.append(gd
.wrapGroup(usr
))
213 ret
.append(md
.wrapUser(usr
))
216 security
.declarePublic('getAllGroupMembers')
217 def getAllGroupMembers(self
, ):
219 Returns a list of the portal_memberdata-ish members of the group.
220 This will include transitive groups / users
222 md
= self
.portal_memberdata
223 gd
= self
.portal_groupdata
225 for u_name
in self
.getGroup().getMemberIds():
226 usr
= self
._getGRUF
().getUserById(u_name
)
228 raise AssertionError, "Cannot retreive a user by its id !"
230 ret
.append(gd
.wrapGroup(usr
))
232 ret
.append(md
.wrapUser(usr
))
235 def _getGroup(self
,):
237 _getGroup(self,) => Get the underlying group object
239 return self
._getGRUF
().getGroupByName(self
.getGroupName())
242 security
.declarePrivate("canAdministrateGroup")
243 def canAdministrateGroup(self
,):
245 Return true if the #current# user can administrate this group
247 user
= getSecurityManager().getUser()
248 tool
= self
.getTool()
249 portal
= getToolByName(tool
, 'portal_url').getPortalObject()
251 # Has manager users pemission?
252 if user
.has_permission(Permissions
.manage_users
, portal
):
255 # Is explicitly mentioned as a group administrator?
256 managers
= self
.getProperty('delegated_group_member_managers', ())
257 if user
.getId() in managers
:
260 # Belongs to a group which is explicitly mentionned as a group administrator
261 meth
= getattr(user
, "getAllGroupNames", None)
270 # No right to edit this: we complain.
273 security
.declarePublic('addMember')
274 def addMember(self
, id, REQUEST
=None):
275 """ Add the existing member with the given id to the group"""
276 # We check if the current user can directly or indirectly administrate this group
277 if not self
.canAdministrateGroup():
278 raise Unauthorized
, "You cannot add a member to the group."
279 self
._getGroup
().addMember(id)
281 # Notify member that they've been changed
282 mtool
= getToolByName(self
, 'portal_membership')
283 member
= mtool
.getMemberById(id)
285 member
.notifyModified()
286 addMember
= postonly(addMember
)
288 security
.declarePublic('removeMember')
289 def removeMember(self
, id, REQUEST
=None):
290 """Remove the member with the provided id from the group.
292 # We check if the current user can directly or indirectly administrate this group
293 if not self
.canAdministrateGroup():
294 raise Unauthorized
, "You cannot remove a member from the group."
295 self
._getGroup
().removeMember(id)
297 # Notify member that they've been changed
298 mtool
= getToolByName(self
, 'portal_membership')
299 member
= mtool
.getMemberById(id)
301 member
.notifyModified()
302 removeMember
= postonly(removeMember
)
304 security
.declareProtected(Permissions
.manage_users
, 'setProperties')
305 def setProperties(self
, properties
=None, **kw
):
306 '''Allows the manager group to set his/her own properties.
307 Accepts either keyword arguments or a mapping for the "properties"
310 if properties
is None:
312 return self
.setGroupProperties(properties
)
314 security
.declareProtected(Permissions
.manage_users
, 'setGroupProperties')
315 def setGroupProperties(self
, mapping
):
316 '''Sets the properties of the member.
318 # Sets the properties given in the MemberDataTool.
319 tool
= self
.getTool()
320 for id in tool
.propertyIds():
321 if mapping
.has_key(id):
322 if not self
.__class
__.__dict
__.has_key(id):
324 if type(value
)==type(''):
325 proptype
= tool
.getPropertyType(id) or 'string'
326 if type_converters
.has_key(proptype
):
327 value
= type_converters
[proptype
](value
)
328 setattr(self
, id, value
)
330 # Hopefully we can later make notifyModified() implicit.
331 self
.notifyModified()
333 security
.declarePublic('getProperties')
334 def getProperties(self
, ):
335 """ Return the properties of this group. Properties are as usual in Zope."""
336 tool
= self
.getTool()
338 for pty
in tool
.propertyIds():
340 ret
[pty
] = self
.getProperty(pty
)
342 # We ignore missing ptys
346 security
.declarePublic('getProperty')
347 def getProperty(self
, id, default
=_marker
):
348 """ Returns the value of the property specified by 'id' """
349 tool
= self
.getTool()
350 base
= aq_base( self
)
352 # First, check the wrapper (w/o acquisition).
353 value
= getattr( base
, id, _marker
)
354 if value
is not _marker
:
357 # Then, check the tool and the user object for a value.
358 tool_value
= tool
.getProperty( id, _marker
)
359 user_value
= getattr( aq_base(self
.getGroup()), id, _marker
)
361 # If the tool doesn't have the property, use user_value or default
362 if tool_value
is _marker
:
363 if user_value
is not _marker
:
365 elif default
is not _marker
:
368 raise ValueError, 'The property %s does not exist' % id
370 # If the tool has an empty property and we have a user_value, use it
371 if not tool_value
and user_value
is not _marker
:
374 # Otherwise return the tool value
378 return self
.getGroupId()
380 security
.declarePublic("isGroup")
383 isGroup(self,) => Return true if this is a group.
384 Will always return true for groups.
385 As MemberData objects do not support this method, it is quite useless by now.
386 So one can use groupstool.isGroup(g) instead to get this information.
390 ### Group object interface ###
392 security
.declarePublic('getGroupName')
393 def getGroupName(self
):
394 """Return the name of the group, without any special decorations (like GRUF prefixes.)"""
395 return self
.getGroup().getName()
397 security
.declarePublic('getGroupId')
398 def getGroupId(self
):
399 """Get the ID of the group. The ID can be used, at least from
400 Python, to get the user from the user's UserDatabase.
401 Within Plone, all group ids are UNPREFIXED."""
402 if isinstance(self
, GRUFGroup
):
403 return self
.getGroup().getId(unprefixed
= 1)
405 return self
.getGroup().getId()
407 def getGroupTitleOrName(self
):
408 """Get the Title property of the group. If there is none
409 then return the name """
410 title
= self
.getProperty('title', None)
411 return title
or self
.getGroupName()
413 security
.declarePublic("getMemberId")
414 def getMemberId(self
,):
415 """This exists only for a basic user/group API compatibility
417 return self
.getGroupId()
419 security
.declarePublic('getRoles')
421 """Return the list of roles assigned to a user."""
422 return self
.getGroup().getRoles()
424 security
.declarePublic('getRolesInContext')
425 def getRolesInContext(self
, object):
426 """Return the list of roles assigned to the user, including local
427 roles assigned in context of the passed in object."""
428 return self
.getGroup().getRolesInContext(object)
430 security
.declarePublic('getDomains')
431 def getDomains(self
):
432 """Return the list of domain restrictions for a user"""
433 return self
.getGroup().getDomains()
435 security
.declarePublic('has_role')
436 def has_role(self
, roles
, object=None):
437 """Check to see if a user has a given role or roles."""
438 return self
.getGroup().has_role(roles
, object)
440 # There are other parts of the interface but they are
441 # deprecated for use with CMF applications.
443 InitializeClass(GroupData
)