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 if hasattr(self
, 'REQUEST'):
120 # No REQUEST during tests.
121 self
.REQUEST
._hold
(CleanupTemp(self
))
123 temps
[id] = portal_group
125 portal_group
= members
[id]
126 # Return a wrapper with self as containment and
127 # the user as context.
128 return portal_group
.__of
__(self
).__of
__(g
)
130 security
.declarePrivate('registerGroupData')
131 def registerGroupData(self
, g
, id):
133 Adds the given member data to the _members dict.
134 This is done as late as possible to avoid side effect
135 transactions and to reduce the necessary number of
138 self
._members
[id] = aq_base(g
)
140 InitializeClass(GroupDataTool
)
143 class GroupData (SimpleItem
):
145 __implements__
= IGroupData
147 security
= ClassSecurityInfo()
152 def __init__(self
, tool
, id):
154 # Make a temporary reference to the tool.
155 # The reference will be removed by notifyModified().
159 return self
.acl_users
161 security
.declarePrivate('notifyModified')
162 def notifyModified(self
):
163 # Links self to parent for full persistence.
164 tool
= getattr(self
, '_tool', None)
167 tool
.registerGroupData(self
, self
.getId())
169 security
.declarePublic('getGroup')
171 """ Returns the actual group implementation. Varies by group
172 implementation (GRUF/Nux/et al). In GRUF this is a user object."""
173 # The user object is our context, but it's possible for
174 # restricted code to strip context while retaining
175 # containment. Therefore we need a simple security check.
176 parent
= aq_parent(self
)
177 bcontext
= aq_base(parent
)
178 bcontainer
= aq_base(aq_parent(aq_inner(self
)))
179 if bcontext
is bcontainer
or not hasattr(bcontext
, 'getUserName'):
180 raise 'GroupDataError', "Can't find group data"
181 # Return the user object, which is our context.
185 return aq_parent(aq_inner(self
))
187 security
.declarePublic("getGroupMemberIds")
188 def getGroupMemberIds(self
,):
190 Return a list of group member ids
192 return map(lambda x
: x
.getMemberId(), self
.getGroupMembers())
194 security
.declarePublic("getAllGroupMemberIds")
195 def getAllGroupMemberIds(self
,):
197 Return a list of group member ids
199 return map(lambda x
: x
.getMemberId(), self
.getAllGroupMembers())
201 security
.declarePublic('getGroupMembers')
202 def getGroupMembers(self
, ):
204 Returns a list of the portal_memberdata-ish members of the group.
205 This doesn't include TRANSITIVE groups/users.
207 md
= self
.portal_memberdata
208 gd
= self
.portal_groupdata
210 for u_name
in self
.getGroup().getMemberIds(transitive
= 0, ):
211 usr
= self
._getGRUF
().getUserById(u_name
)
213 raise AssertionError, "Cannot retreive a user by its id !"
215 ret
.append(gd
.wrapGroup(usr
))
217 ret
.append(md
.wrapUser(usr
))
220 security
.declarePublic('getAllGroupMembers')
221 def getAllGroupMembers(self
, ):
223 Returns a list of the portal_memberdata-ish members of the group.
224 This will include transitive groups / users
226 md
= self
.portal_memberdata
227 gd
= self
.portal_groupdata
229 for u_name
in self
.getGroup().getMemberIds():
230 usr
= self
._getGRUF
().getUserById(u_name
)
232 raise AssertionError, "Cannot retreive a user by its id !"
234 ret
.append(gd
.wrapGroup(usr
))
236 ret
.append(md
.wrapUser(usr
))
239 def _getGroup(self
,):
241 _getGroup(self,) => Get the underlying group object
243 return self
._getGRUF
().getGroupByName(self
.getGroupName())
246 security
.declarePrivate("canAdministrateGroup")
247 def canAdministrateGroup(self
,):
249 Return true if the #current# user can administrate this group
251 user
= getSecurityManager().getUser()
252 tool
= self
.getTool()
253 portal
= getToolByName(tool
, 'portal_url').getPortalObject()
255 # Has manager users pemission?
256 if user
.has_permission(Permissions
.manage_users
, portal
):
259 # Is explicitly mentioned as a group administrator?
260 managers
= self
.getProperty('delegated_group_member_managers', ())
261 if user
.getId() in managers
:
264 # Belongs to a group which is explicitly mentionned as a group administrator
265 meth
= getattr(user
, "getAllGroupNames", None)
274 # No right to edit this: we complain.
277 security
.declarePublic('addMember')
278 def addMember(self
, id, REQUEST
=None):
279 """ Add the existing member with the given id to the group"""
280 # We check if the current user can directly or indirectly administrate this group
281 if not self
.canAdministrateGroup():
282 raise Unauthorized
, "You cannot add a member to the group."
283 self
._getGroup
().addMember(id)
285 # Notify member that they've been changed
286 mtool
= getToolByName(self
, 'portal_membership')
287 member
= mtool
.getMemberById(id)
289 member
.notifyModified()
290 addMember
= postonly(addMember
)
292 security
.declarePublic('removeMember')
293 def removeMember(self
, id, REQUEST
=None):
294 """Remove the member with the provided id from the group.
296 # We check if the current user can directly or indirectly administrate this group
297 if not self
.canAdministrateGroup():
298 raise Unauthorized
, "You cannot remove a member from the group."
299 self
._getGroup
().removeMember(id)
301 # Notify member that they've been changed
302 mtool
= getToolByName(self
, 'portal_membership')
303 member
= mtool
.getMemberById(id)
305 member
.notifyModified()
306 removeMember
= postonly(removeMember
)
308 security
.declareProtected(Permissions
.manage_users
, 'setProperties')
309 def setProperties(self
, properties
=None, **kw
):
310 '''Allows the manager group to set his/her own properties.
311 Accepts either keyword arguments or a mapping for the "properties"
314 if properties
is None:
316 return self
.setGroupProperties(properties
)
318 security
.declareProtected(Permissions
.manage_users
, 'setGroupProperties')
319 def setGroupProperties(self
, mapping
):
320 '''Sets the properties of the member.
322 # Sets the properties given in the MemberDataTool.
323 tool
= self
.getTool()
324 for id in tool
.propertyIds():
325 if mapping
.has_key(id):
326 if not self
.__class
__.__dict
__.has_key(id):
328 if type(value
)==type(''):
329 proptype
= tool
.getPropertyType(id) or 'string'
330 if type_converters
.has_key(proptype
):
331 value
= type_converters
[proptype
](value
)
332 setattr(self
, id, value
)
334 # Hopefully we can later make notifyModified() implicit.
335 self
.notifyModified()
337 security
.declarePublic('getProperties')
338 def getProperties(self
, ):
339 """ Return the properties of this group. Properties are as usual in Zope."""
340 tool
= self
.getTool()
342 for pty
in tool
.propertyIds():
344 ret
[pty
] = self
.getProperty(pty
)
346 # We ignore missing ptys
350 security
.declarePublic('getProperty')
351 def getProperty(self
, id, default
=_marker
):
352 """ Returns the value of the property specified by 'id' """
353 tool
= self
.getTool()
354 base
= aq_base( self
)
356 # First, check the wrapper (w/o acquisition).
357 value
= getattr( base
, id, _marker
)
358 if value
is not _marker
:
361 # Then, check the tool and the user object for a value.
362 tool_value
= tool
.getProperty( id, _marker
)
363 user_value
= getattr( aq_base(self
.getGroup()), id, _marker
)
365 # If the tool doesn't have the property, use user_value or default
366 if tool_value
is _marker
:
367 if user_value
is not _marker
:
369 elif default
is not _marker
:
372 raise ValueError, 'The property %s does not exist' % id
374 # If the tool has an empty property and we have a user_value, use it
375 if not tool_value
and user_value
is not _marker
:
378 # Otherwise return the tool value
382 return self
.getGroupId()
384 security
.declarePublic("isGroup")
387 isGroup(self,) => Return true if this is a group.
388 Will always return true for groups.
389 As MemberData objects do not support this method, it is quite useless by now.
390 So one can use groupstool.isGroup(g) instead to get this information.
394 ### Group object interface ###
396 security
.declarePublic('getGroupName')
397 def getGroupName(self
):
398 """Return the name of the group, without any special decorations (like GRUF prefixes.)"""
399 return self
.getGroup().getName()
401 security
.declarePublic('getGroupId')
402 def getGroupId(self
):
403 """Get the ID of the group. The ID can be used, at least from
404 Python, to get the user from the user's UserDatabase.
405 Within Plone, all group ids are UNPREFIXED."""
406 if isinstance(self
, GRUFGroup
):
407 return self
.getGroup().getId(unprefixed
= 1)
409 return self
.getGroup().getId()
411 def getGroupTitleOrName(self
):
412 """Get the Title property of the group. If there is none
413 then return the name """
414 title
= self
.getProperty('title', None)
415 return title
or self
.getGroupName()
417 security
.declarePublic("getMemberId")
418 def getMemberId(self
,):
419 """This exists only for a basic user/group API compatibility
421 return self
.getGroupId()
423 security
.declarePublic('getRoles')
425 """Return the list of roles assigned to a user."""
426 return self
.getGroup().getRoles()
428 security
.declarePublic('getRolesInContext')
429 def getRolesInContext(self
, object):
430 """Return the list of roles assigned to the user, including local
431 roles assigned in context of the passed in object."""
432 return self
.getGroup().getRolesInContext(object)
434 security
.declarePublic('getDomains')
435 def getDomains(self
):
436 """Return the list of domain restrictions for a user"""
437 return self
.getGroup().getDomains()
439 security
.declarePublic('has_role')
440 def has_role(self
, roles
, object=None):
441 """Check to see if a user has a given role or roles."""
442 return self
.getGroup().has_role(roles
, object)
444 # There are other parts of the interface but they are
445 # deprecated for use with CMF applications.
447 InitializeClass(GroupData
)