9b0532603291633c797874cfc3f3d208761b5e0e
[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
41 from Products.CMFCore.ActionProviderBase import ActionProviderBase
42 # BBB CMF < 1.5
43 try:
44 from Products.CMFCore.permissions import ManagePortal
45 except ImportError:
46 from Products.CMFCore.CMFCorePermissions import ManagePortal
47
48 from Products.CMFCore.MemberDataTool import CleanupTemp
49
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
54
55 _marker = [] # Create a new marker object.
56
57 from global_symbols import *
58
59
60 class GroupDataTool (UniqueObject, SimpleItem, PropertyManager, ActionProviderBase):
61 """ This tool wraps group objects, allowing transparent access to properties.
62 """
63 # The latter will work only with Plone 1.1 => hence, the if
64 __implements__ = (IGroupDataTool, ActionProviderBase.__implements__)
65
66 id = 'portal_groupdata'
67 meta_type = 'CMF Group Data Tool'
68 _actions = ()
69
70 _v_temps = None
71 _properties=({'id':'title', 'type': 'string', 'mode': 'wd'},)
72
73 security = ClassSecurityInfo()
74
75 manage_options=( ActionProviderBase.manage_options +
76 ({ 'label' : 'Overview'
77 , 'action' : 'manage_overview'
78 },
79 )
80 + PropertyManager.manage_options
81 + SimpleItem.manage_options
82 )
83
84 #
85 # ZMI methods
86 #
87 security.declareProtected(ManagePortal, 'manage_overview')
88 manage_overview = DTMLFile('dtml/explainGroupDataTool', globals())
89
90 def __init__(self):
91 self._members = OOBTree()
92 # Create the default properties.
93 self._setProperty('description', '', 'text')
94 self._setProperty('email', '', 'string')
95
96 #
97 # 'portal_groupdata' interface methods
98 #
99 security.declarePrivate('wrapGroup')
100 def wrapGroup(self, g):
101 """Returns an object implementing the GroupData interface"""
102 id = g.getId()
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]
110 else:
111 base = aq_base(self)
112 portal_group = GroupData(base, id)
113 if temps is None:
114 self._v_temps = {id:portal_group}
115 if hasattr(self, 'REQUEST'):
116 # No REQUEST during tests.
117 self.REQUEST._hold(CleanupTemp(self))
118 else:
119 temps[id] = portal_group
120 else:
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)
125
126 security.declarePrivate('registerGroupData')
127 def registerGroupData(self, g, id):
128 '''
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
132 entries.
133 '''
134 self._members[id] = aq_base(g)
135
136 InitializeClass(GroupDataTool)
137
138
139 class GroupData (SimpleItem):
140
141 __implements__ = IGroupData
142
143 security = ClassSecurityInfo()
144
145 id = None
146 _tool = None
147
148 def __init__(self, tool, id):
149 self.id = id
150 # Make a temporary reference to the tool.
151 # The reference will be removed by notifyModified().
152 self._tool = tool
153
154 def _getGRUF(self,):
155 return self.acl_users
156
157 security.declarePrivate('notifyModified')
158 def notifyModified(self):
159 # Links self to parent for full persistence.
160 tool = getattr(self, '_tool', None)
161 if tool is not None:
162 del self._tool
163 tool.registerGroupData(self, self.getId())
164
165 security.declarePublic('getGroup')
166 def getGroup(self):
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.
178 return parent
179
180 def getTool(self):
181 return aq_parent(aq_inner(self))
182
183 security.declarePublic("getGroupMemberIds")
184 def getGroupMemberIds(self,):
185 """
186 Return a list of group member ids
187 """
188 return map(lambda x: x.getMemberId(), self.getGroupMembers())
189
190 security.declarePublic("getAllGroupMemberIds")
191 def getAllGroupMemberIds(self,):
192 """
193 Return a list of group member ids
194 """
195 return map(lambda x: x.getMemberId(), self.getAllGroupMembers())
196
197 security.declarePublic('getGroupMembers')
198 def getGroupMembers(self, ):
199 """
200 Returns a list of the portal_memberdata-ish members of the group.
201 This doesn't include TRANSITIVE groups/users.
202 """
203 md = self.portal_memberdata
204 gd = self.portal_groupdata
205 ret = []
206 for u_name in self.getGroup().getMemberIds(transitive = 0, ):
207 usr = self._getGRUF().getUserById(u_name)
208 if not usr:
209 raise AssertionError, "Cannot retreive a user by its id !"
210 if usr.isGroup():
211 ret.append(gd.wrapGroup(usr))
212 else:
213 ret.append(md.wrapUser(usr))
214 return ret
215
216 security.declarePublic('getAllGroupMembers')
217 def getAllGroupMembers(self, ):
218 """
219 Returns a list of the portal_memberdata-ish members of the group.
220 This will include transitive groups / users
221 """
222 md = self.portal_memberdata
223 gd = self.portal_groupdata
224 ret = []
225 for u_name in self.getGroup().getMemberIds():
226 usr = self._getGRUF().getUserById(u_name)
227 if not usr:
228 raise AssertionError, "Cannot retreive a user by its id !"
229 if usr.isGroup():
230 ret.append(gd.wrapGroup(usr))
231 else:
232 ret.append(md.wrapUser(usr))
233 return ret
234
235 def _getGroup(self,):
236 """
237 _getGroup(self,) => Get the underlying group object
238 """
239 return self._getGRUF().getGroupByName(self.getGroupName())
240
241
242 security.declarePrivate("canAdministrateGroup")
243 def canAdministrateGroup(self,):
244 """
245 Return true if the #current# user can administrate this group
246 """
247 user = getSecurityManager().getUser()
248 tool = self.getTool()
249 portal = getToolByName(tool, 'portal_url').getPortalObject()
250
251 # Has manager users pemission?
252 if user.has_permission(Permissions.manage_users, portal):
253 return True
254
255 # Is explicitly mentioned as a group administrator?
256 managers = self.getProperty('delegated_group_member_managers', ())
257 if user.getId() in managers:
258 return True
259
260 # Belongs to a group which is explicitly mentionned as a group administrator
261 meth = getattr(user, "getAllGroupNames", None)
262 if meth:
263 groups = meth()
264 else:
265 groups = ()
266 for v in groups:
267 if v in managers:
268 return True
269
270 # No right to edit this: we complain.
271 return False
272
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)
280
281 # Notify member that they've been changed
282 mtool = getToolByName(self, 'portal_membership')
283 member = mtool.getMemberById(id)
284 if member:
285 member.notifyModified()
286 addMember = postonly(addMember)
287
288 security.declarePublic('removeMember')
289 def removeMember(self, id, REQUEST=None):
290 """Remove the member with the provided id from the group.
291 """
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)
296
297 # Notify member that they've been changed
298 mtool = getToolByName(self, 'portal_membership')
299 member = mtool.getMemberById(id)
300 if member:
301 member.notifyModified()
302 removeMember = postonly(removeMember)
303
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"
308 argument.
309 '''
310 if properties is None:
311 properties = kw
312 return self.setGroupProperties(properties)
313
314 security.declareProtected(Permissions.manage_users, 'setGroupProperties')
315 def setGroupProperties(self, mapping):
316 '''Sets the properties of the member.
317 '''
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):
323 value = mapping[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)
329
330 # Hopefully we can later make notifyModified() implicit.
331 self.notifyModified()
332
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()
337 ret = {}
338 for pty in tool.propertyIds():
339 try:
340 ret[pty] = self.getProperty(pty)
341 except ValueError:
342 # We ignore missing ptys
343 continue
344 return ret
345
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 )
351
352 # First, check the wrapper (w/o acquisition).
353 value = getattr( base, id, _marker )
354 if value is not _marker:
355 return value
356
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 )
360
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:
364 return user_value
365 elif default is not _marker:
366 return default
367 else:
368 raise ValueError, 'The property %s does not exist' % id
369
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:
372 return user_value
373
374 # Otherwise return the tool value
375 return tool_value
376
377 def __str__(self):
378 return self.getGroupId()
379
380 security.declarePublic("isGroup")
381 def isGroup(self,):
382 """
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.
387 """
388 return 1
389
390 ### Group object interface ###
391
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()
396
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)
404 else:
405 return self.getGroup().getId()
406
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()
412
413 security.declarePublic("getMemberId")
414 def getMemberId(self,):
415 """This exists only for a basic user/group API compatibility
416 """
417 return self.getGroupId()
418
419 security.declarePublic('getRoles')
420 def getRoles(self):
421 """Return the list of roles assigned to a user."""
422 return self.getGroup().getRoles()
423
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)
429
430 security.declarePublic('getDomains')
431 def getDomains(self):
432 """Return the list of domain restrictions for a user"""
433 return self.getGroup().getDomains()
434
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)
439
440 # There are other parts of the interface but they are
441 # deprecated for use with CMF applications.
442
443 InitializeClass(GroupData)