2da24933f538e76f4687f3ae16361533898e0f45
[GroupUserFolder.git] / GroupDataTool.py
1 # -*- coding: utf-8 -*-
2 ## GroupUserFolder
3 ## Copyright (C)2006 Ingeniweb
4
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.
9
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.
14
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.
18
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
22 """
23 Basic group data tool.
24 """
25 __version__ = "$Revision: $"
26 # $Source: $
27 # $Id: GroupDataTool.py 52136 2007-10-21 20:38:00Z encolpe $
28 __docformat__ = 'restructuredtext'
29
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
42
43 from Products.CMFCore.ActionProviderBase import ActionProviderBase
44
45 # BBB CMF < 1.5
46 try:
47 from Products.CMFCore.permissions import ManagePortal
48 except ImportError:
49 from Products.CMFCore.CMFCorePermissions import ManagePortal
50
51 from Products.CMFCore.MemberDataTool import CleanupTemp
52
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
57
58 _marker = [] # Create a new marker object.
59
60 from global_symbols import *
61
62
63 class GroupDataTool (UniqueObject, SimpleItem, PropertyManager, ActionProviderBase):
64 """ This tool wraps group objects, allowing transparent access to properties.
65 """
66 # The latter will work only with Plone 1.1 => hence, the if
67 implements(IGroupDataTool, IActionProvider)
68 # __implements__ = (IGroupDataTool, ActionProviderBase.__implements__)
69
70 id = 'portal_groupdata'
71 meta_type = 'CMF Group Data Tool'
72 _actions = ()
73
74 _v_temps = None
75 _properties=({'id':'title', 'type': 'string', 'mode': 'wd'},)
76
77 security = ClassSecurityInfo()
78
79 manage_options=( ActionProviderBase.manage_options +
80 ({ 'label' : 'Overview'
81 , 'action' : 'manage_overview'
82 },
83 )
84 + PropertyManager.manage_options
85 + SimpleItem.manage_options
86 )
87
88 #
89 # ZMI methods
90 #
91 security.declareProtected(ManagePortal, 'manage_overview')
92 manage_overview = DTMLFile('dtml/explainGroupDataTool', globals())
93
94 def __init__(self):
95 self._members = OOBTree()
96 # Create the default properties.
97 self._setProperty('description', '', 'text')
98 self._setProperty('email', '', 'string')
99
100 #
101 # 'portal_groupdata' interface methods
102 #
103 security.declarePrivate('wrapGroup')
104 def wrapGroup(self, g):
105 """Returns an object implementing the GroupData interface"""
106 id = g.getId()
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]
114 else:
115 base = aq_base(self)
116 portal_group = GroupData(base, id)
117 if temps is None:
118 self._v_temps = {id:portal_group}
119 if hasattr(self, 'REQUEST'):
120 # No REQUEST during tests.
121 self.REQUEST._hold(CleanupTemp(self))
122 else:
123 temps[id] = portal_group
124 else:
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)
129
130 security.declarePrivate('registerGroupData')
131 def registerGroupData(self, g, id):
132 '''
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
136 entries.
137 '''
138 self._members[id] = aq_base(g)
139
140 InitializeClass(GroupDataTool)
141
142
143 class GroupData (SimpleItem):
144
145 __implements__ = IGroupData
146
147 security = ClassSecurityInfo()
148
149 id = None
150 _tool = None
151
152 def __init__(self, tool, id):
153 self.id = id
154 # Make a temporary reference to the tool.
155 # The reference will be removed by notifyModified().
156 self._tool = tool
157
158 def _getGRUF(self,):
159 return self.acl_users
160
161 security.declarePrivate('notifyModified')
162 def notifyModified(self):
163 # Links self to parent for full persistence.
164 tool = getattr(self, '_tool', None)
165 if tool is not None:
166 del self._tool
167 tool.registerGroupData(self, self.getId())
168
169 security.declarePublic('getGroup')
170 def getGroup(self):
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.
182 return parent
183
184 def getTool(self):
185 return aq_parent(aq_inner(self))
186
187 security.declarePublic("getGroupMemberIds")
188 def getGroupMemberIds(self,):
189 """
190 Return a list of group member ids
191 """
192 return map(lambda x: x.getMemberId(), self.getGroupMembers())
193
194 security.declarePublic("getAllGroupMemberIds")
195 def getAllGroupMemberIds(self,):
196 """
197 Return a list of group member ids
198 """
199 return map(lambda x: x.getMemberId(), self.getAllGroupMembers())
200
201 security.declarePublic('getGroupMembers')
202 def getGroupMembers(self, ):
203 """
204 Returns a list of the portal_memberdata-ish members of the group.
205 This doesn't include TRANSITIVE groups/users.
206 """
207 md = self.portal_memberdata
208 gd = self.portal_groupdata
209 ret = []
210 for u_name in self.getGroup().getMemberIds(transitive = 0, ):
211 usr = self._getGRUF().getUserById(u_name)
212 if not usr:
213 raise AssertionError, "Cannot retreive a user by its id !"
214 if usr.isGroup():
215 ret.append(gd.wrapGroup(usr))
216 else:
217 ret.append(md.wrapUser(usr))
218 return ret
219
220 security.declarePublic('getAllGroupMembers')
221 def getAllGroupMembers(self, ):
222 """
223 Returns a list of the portal_memberdata-ish members of the group.
224 This will include transitive groups / users
225 """
226 md = self.portal_memberdata
227 gd = self.portal_groupdata
228 ret = []
229 for u_name in self.getGroup().getMemberIds():
230 usr = self._getGRUF().getUserById(u_name)
231 if not usr:
232 raise AssertionError, "Cannot retreive a user by its id !"
233 if usr.isGroup():
234 ret.append(gd.wrapGroup(usr))
235 else:
236 ret.append(md.wrapUser(usr))
237 return ret
238
239 def _getGroup(self,):
240 """
241 _getGroup(self,) => Get the underlying group object
242 """
243 return self._getGRUF().getGroupByName(self.getGroupName())
244
245
246 security.declarePrivate("canAdministrateGroup")
247 def canAdministrateGroup(self,):
248 """
249 Return true if the #current# user can administrate this group
250 """
251 user = getSecurityManager().getUser()
252 tool = self.getTool()
253 portal = getToolByName(tool, 'portal_url').getPortalObject()
254
255 # Has manager users pemission?
256 if user.has_permission(Permissions.manage_users, portal):
257 return True
258
259 # Is explicitly mentioned as a group administrator?
260 managers = self.getProperty('delegated_group_member_managers', ())
261 if user.getId() in managers:
262 return True
263
264 # Belongs to a group which is explicitly mentionned as a group administrator
265 meth = getattr(user, "getAllGroupNames", None)
266 if meth:
267 groups = meth()
268 else:
269 groups = ()
270 for v in groups:
271 if v in managers:
272 return True
273
274 # No right to edit this: we complain.
275 return False
276
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)
284
285 # Notify member that they've been changed
286 mtool = getToolByName(self, 'portal_membership')
287 member = mtool.getMemberById(id)
288 if member:
289 member.notifyModified()
290 addMember = postonly(addMember)
291
292 security.declarePublic('removeMember')
293 def removeMember(self, id, REQUEST=None):
294 """Remove the member with the provided id from the group.
295 """
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)
300
301 # Notify member that they've been changed
302 mtool = getToolByName(self, 'portal_membership')
303 member = mtool.getMemberById(id)
304 if member:
305 member.notifyModified()
306 removeMember = postonly(removeMember)
307
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"
312 argument.
313 '''
314 if properties is None:
315 properties = kw
316 return self.setGroupProperties(properties)
317
318 security.declareProtected(Permissions.manage_users, 'setGroupProperties')
319 def setGroupProperties(self, mapping):
320 '''Sets the properties of the member.
321 '''
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):
327 value = mapping[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)
333
334 # Hopefully we can later make notifyModified() implicit.
335 self.notifyModified()
336
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()
341 ret = {}
342 for pty in tool.propertyIds():
343 try:
344 ret[pty] = self.getProperty(pty)
345 except ValueError:
346 # We ignore missing ptys
347 continue
348 return ret
349
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 )
355
356 # First, check the wrapper (w/o acquisition).
357 value = getattr( base, id, _marker )
358 if value is not _marker:
359 return value
360
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 )
364
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:
368 return user_value
369 elif default is not _marker:
370 return default
371 else:
372 raise ValueError, 'The property %s does not exist' % id
373
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:
376 return user_value
377
378 # Otherwise return the tool value
379 return tool_value
380
381 def __str__(self):
382 return self.getGroupId()
383
384 security.declarePublic("isGroup")
385 def isGroup(self,):
386 """
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.
391 """
392 return 1
393
394 ### Group object interface ###
395
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()
400
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)
408 else:
409 return self.getGroup().getId()
410
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()
416
417 security.declarePublic("getMemberId")
418 def getMemberId(self,):
419 """This exists only for a basic user/group API compatibility
420 """
421 return self.getGroupId()
422
423 security.declarePublic('getRoles')
424 def getRoles(self):
425 """Return the list of roles assigned to a user."""
426 return self.getGroup().getRoles()
427
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)
433
434 security.declarePublic('getDomains')
435 def getDomains(self):
436 """Return the list of domain restrictions for a user"""
437 return self.getGroup().getDomains()
438
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)
443
444 # There are other parts of the interface but they are
445 # deprecated for use with CMF applications.
446
447 InitializeClass(GroupData)