8d6d85a45e6be4fac8a43d6efa9df92c3fe04a2e
[GroupUserFolder.git] / GroupUserFolder.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 GroupUserFolder product
20 """
21 __version__ = "$Revision: $"
22 # $Source: $
23 # $Id: GroupUserFolder.py 40118 2007-04-01 15:13:44Z alecm $
24 __docformat__ = 'restructuredtext'
25
26
27 # fakes a method from a DTML file
28 from Globals import MessageDialog, DTMLFile
29
30 from AccessControl import ClassSecurityInfo
31 from AccessControl import Permissions
32 from AccessControl import getSecurityManager
33 from AccessControl import Unauthorized
34 from Globals import InitializeClass
35 from Acquisition import aq_base, aq_inner, aq_parent
36 from Acquisition import Implicit
37 from Globals import Persistent
38 from AccessControl.Role import RoleManager
39 from OFS.SimpleItem import Item
40 from OFS.PropertyManager import PropertyManager
41 import OFS
42 from OFS import ObjectManager, SimpleItem
43 from DateTime import DateTime
44 from App import ImageFile
45 from Products.PageTemplates import PageTemplateFile
46 import AccessControl.Role, webdav.Collection
47 import Products
48 import os
49 import string
50 import sys
51 import time
52 import math
53 import random
54 from global_symbols import *
55 import AccessControl.User
56 import GRUFFolder
57 import GRUFUser
58 from Products.PageTemplates import PageTemplateFile
59 import class_utility
60 from Products.GroupUserFolder import postonly
61
62 from interfaces.IUserFolder import IUserFolder
63
64 ## Developers notes
65 ##
66 ## The REQUEST.GRUF_PROBLEM variable is defined whenever GRUF encounters
67 ## a problem than can be showed in the management screens. It's always
68 ## logged as LOG_WARNING level anyway.
69
70 _marker = []
71
72 def unique(sequence, _list = 0):
73 """Make a sequence a list of unique items"""
74 uniquedict = {}
75 for v in sequence:
76 uniquedict[v] = 1
77 if _list:
78 return list(uniquedict.keys())
79 return tuple(uniquedict.keys())
80
81
82 def manage_addGroupUserFolder(self, dtself=None, REQUEST=None, **ignored):
83 """ Factory method that creates a UserFolder"""
84 f=GroupUserFolder()
85 self=self.this()
86 try: self._setObject('acl_users', f)
87 except: return MessageDialog(
88 title ='Item Exists',
89 message='This object already contains a User Folder',
90 action ='%s/manage_main' % REQUEST['URL1'])
91 self.__allow_groups__=f
92 self.acl_users._post_init()
93
94 self.acl_users.Users.manage_addUserFolder()
95 self.acl_users.Groups.manage_addUserFolder()
96
97 if REQUEST is not None:
98 REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
99
100
101
102
103 class GroupUserFolder(OFS.ObjectManager.ObjectManager,
104 AccessControl.User.BasicUserFolder,
105 ):
106 """
107 GroupUserFolder => User folder with groups management
108 """
109
110 # #
111 # ZOPE INFORMATION #
112 # #
113
114 meta_type='Group User Folder'
115 id ='acl_users'
116 title ='Group-aware User Folder'
117
118 __implements__ = (IUserFolder, )
119 def __creatable_by_emergency_user__(self): return 1
120
121 isAnObjectManager = 1
122 isPrincipiaFolderish = 1
123 isAUserFolder = 1
124
125 ## _haveLDAPUF = 0
126
127 security = ClassSecurityInfo()
128
129 manage_options=(
130 (
131 {'label':'Overview', 'action':'manage_overview'},
132 {'label':'Sources', 'action':'manage_GRUFSources'},
133 {'label':'LDAP Wizard', 'action':'manage_wizard'},
134 {'label':'Groups', 'action':'manage_groups'},
135 {'label':'Users', 'action':'manage_users'},
136 {'label':'Audit', 'action':'manage_audit'},
137 ) + \
138 OFS.ObjectManager.ObjectManager.manage_options + \
139 RoleManager.manage_options + \
140 Item.manage_options )
141
142 manage_main = OFS.ObjectManager.ObjectManager.manage_main
143 ## manage_overview = DTMLFile('dtml/GRUF_overview', globals())
144 manage_overview = PageTemplateFile.PageTemplateFile('dtml/GRUF_overview', globals())
145 manage_audit = PageTemplateFile.PageTemplateFile('dtml/GRUF_audit', globals())
146 manage_wizard = PageTemplateFile.PageTemplateFile('dtml/GRUF_wizard', globals())
147 manage_groups = PageTemplateFile.PageTemplateFile('dtml/GRUF_groups', globals())
148 manage_users = PageTemplateFile.PageTemplateFile('dtml/GRUF_users', globals())
149 manage_newusers = PageTemplateFile.PageTemplateFile('dtml/GRUF_newusers', globals())
150 manage_GRUFSources = PageTemplateFile.PageTemplateFile('dtml/GRUF_contents', globals())
151 manage_user = PageTemplateFile.PageTemplateFile('dtml/GRUF_user', globals())
152
153 __ac_permissions__=(
154 ('Manage users',
155 ('manage_users',
156 'user_names', 'setDomainAuthenticationMode',
157 )
158 ),
159 )
160
161
162 # Color constants, only useful within GRUF management screens
163 user_color = "#006600"
164 group_color = "#000099"
165 role_color = "#660000"
166
167 # User and group images
168 img_user = ImageFile.ImageFile('www/GRUFUsers.gif', globals())
169 img_group = ImageFile.ImageFile('www/GRUFGroups.gif', globals())
170
171
172
173 # #
174 # OFFICIAL INTERFACE #
175 # #
176
177 security.declarePublic("hasUsers")
178 def hasUsers(self, ):
179 """
180 From Zope 2.7's User.py:
181 This is not a formal API method: it is used only to provide
182 a way for the quickstart page to determine if the default user
183 folder contains any users to provide instructions on how to
184 add a user for newbies. Using getUserNames or getUsers would have
185 posed a denial of service risk.
186 In GRUF, this method always return 1."""
187 return 1
188
189 security.declareProtected(Permissions.manage_users, "user_names")
190 def user_names(self,):
191 """
192 user_names() => return user IDS and not user NAMES !!!
193 Due to a Zope inconsistency, the Role.get_valid_userids return user names
194 and not user ids - which is bad. As GRUF distinguishes names and ids, this
195 will cause it to break, especially in the listLocalRoles form. So we change
196 user_names() behaviour so that it will return ids and not names.
197 """
198 return self.getUserIds()
199
200
201 security.declareProtected(Permissions.manage_users, "getUserNames")
202 def getUserNames(self, __include_groups__ = 1, __include_users__ = 1, __groups_prefixed__ = 0):
203 """
204 Return a list of all possible user atom names in the system.
205 Groups will be returned WITHOUT their prefix by this method.
206 So, there might be a collision between a user name and a group name.
207 [NOTA: This method is time-expensive !]
208 """
209 if __include_users__:
210 LogCallStack(LOG_DEBUG, "This call can be VERY expensive!")
211 names = []
212 ldap_sources = []
213
214 # Fetch users in user sources
215 if __include_users__:
216 for src in self.listUserSources():
217 names.extend(src.getUserNames())
218
219 # Append groups if possible
220 if __include_groups__:
221 # Regular groups
222 if "acl_users" in self._getOb('Groups').objectIds():
223 names.extend(self.Groups.listGroups(prefixed = __groups_prefixed__))
224
225 # LDAP groups
226 for ldapuf in ldap_sources:
227 if ldapuf._local_groups:
228 continue
229 for g in ldapuf.getGroups(attr = LDAP_GROUP_RDN):
230 if __groups_prefixed__:
231 names.append("%s%s" % (GROUP_PREFIX, g))
232 else:
233 names.append(g)
234 # Return a list of unique names
235 return unique(names, _list = 1)
236
237 security.declareProtected(Permissions.manage_users, "getUserIds")
238 def getUserIds(self,):
239 """
240 Return a list of all possible user atom ids in the system.
241 WARNING: Please see the id Vs. name consideration at the
242 top of this document. So, groups will be returned
243 WITH their prefix by this method
244 [NOTA: This method is time-expensive !]
245 """
246 return self.getUserNames(__groups_prefixed__ = 1)
247
248 security.declareProtected(Permissions.manage_users, "getUsers")
249 def getUsers(self, __include_groups__ = 1, __include_users__ = 1):
250 """Return a list of user and group objects.
251 In case of some UF implementations, the returned object may only be a subset
252 of all possible users.
253 In other words, you CANNOT assert that len(getUsers()) equals len(getUserNames()).
254 With cache-support UserFolders, such as LDAPUserFolder, the getUser() method will
255 return only cached user objects instead of fetching all possible users.
256 """
257 Log(LOG_DEBUG, "getUsers")
258 ret = []
259 names_set = {}
260
261 # avoid too many lookups for 'has_key' in loops
262 isUserProcessed = names_set.has_key
263
264 # Fetch groups first (then the user must be
265 # prefixed by 'group_' prefix)
266 if __include_groups__:
267 # Fetch regular groups
268 for u in self._getOb('Groups').acl_users.getUsers():
269 if not u:
270 continue # Ignore empty users
271
272 name = u.getId()
273 if isUserProcessed(name):
274 continue # Prevent double users inclusion
275
276 # Append group
277 names_set[name] = True
278 ret.append(
279 GRUFUser.GRUFGroup(u, self, isGroup = 1, source_id = "Groups").__of__(self)
280 )
281
282 # Fetch users then
283 if __include_users__:
284 for src in self.listUserSources():
285 for u in src.getUsers():
286 if not u:
287 continue # Ignore empty users
288
289 name = u.getId()
290 if isUserProcessed(name):
291 continue # Prevent double users inclusion
292
293 # Append user
294 names_set[name] = True
295 ret.append(
296 GRUFUser.GRUFUser(u, self, source_id = src.getUserSourceId(), isGroup = 0).__of__(self)
297 )
298
299 return tuple(ret)
300
301 security.declareProtected(Permissions.manage_users, "getUser")
302 def getUser(self, name, __include_users__ = 1, __include_groups__ = 1, __force_group_id__ = 0):
303 """
304 Return the named user object or None.
305 User have precedence over group.
306 If name is None, getUser() will return None.
307 """
308 # Basic check
309 if name is None:
310 return None
311
312 # Prevent infinite recursion when instanciating a GRUF
313 # without having sub-acl_users set
314 if not "acl_users" in self._getOb('Groups').objectIds():
315 return None
316
317 # Fetch groups first (then the user must be prefixed by 'group_' prefix)
318 if __include_groups__ and name.startswith(GROUP_PREFIX):
319 id = name[GROUP_PREFIX_LEN:]
320
321 # Fetch regular groups
322 u = self._getOb('Groups')._getGroup(id)
323 if u:
324 ret = GRUFUser.GRUFGroup(
325 u, self, isGroup = 1, source_id = "Groups"
326 ).__of__(self)
327 return ret # XXX This violates precedence
328
329 # Fetch users then
330 if __include_users__:
331 for src in self.listUserSources():
332 u = src.getUser(name)
333 if u:
334 ret = GRUFUser.GRUFUser(u, self, source_id = src.getUserSourceId(), isGroup = 0).__of__(self)
335 return ret
336
337 # Then desperatly try to fetch groups (without beeing prefixed by 'group_' prefix)
338 if __include_groups__ and (not __force_group_id__):
339 u = self._getOb('Groups')._getGroup(name)
340 if u:
341 ret = GRUFUser.GRUFGroup(u, self, isGroup = 1, source_id = "Groups").__of__(self)
342 return ret
343
344 return None
345
346
347 security.declareProtected(Permissions.manage_users, "getUserById")
348 def getUserById(self, id, default=_marker):
349 """Return the user atom corresponding to the given id. Can return groups.
350 """
351 ret = self.getUser(id, __force_group_id__ = 1)
352 if not ret:
353 if default is _marker:
354 return None
355 ret = default
356 return ret
357
358
359 security.declareProtected(Permissions.manage_users, "getUserByName")
360 def getUserByName(self, name, default=_marker):
361 """Same as getUser() but works with a name instead of an id.
362 [NOTA: Theorically, the id is a handle, while the name is the actual login name.
363 But difference between a user id and a user name is unsignificant in
364 all current User Folder implementations... except for GROUPS.]
365 """
366 # Try to fetch a user first
367 usr = self.getUser(name)
368
369 # If not found, try to fetch a group by appending the prefix
370 if not usr:
371 name = "%s%s" % (GROUP_PREFIX, name)
372 usr = self.getUserById(name, default)
373
374 return usr
375
376 security.declareProtected(Permissions.manage_users, "getPureUserNames")
377 def getPureUserNames(self, ):
378 """Fetch the list of actual users from GRUFUsers.
379 """
380 return self.getUserNames(__include_groups__ = 0)
381
382
383 security.declareProtected(Permissions.manage_users, "getPureUserIds")
384 def getPureUserIds(self,):
385 """Same as getUserIds() but without groups
386 """
387 return self.getUserNames(__include_groups__ = 0)
388
389 security.declareProtected(Permissions.manage_users, "getPureUsers")
390 def getPureUsers(self):
391 """Return a list of pure user objects.
392 """
393 return self.getUsers(__include_groups__ = 0)
394
395 security.declareProtected(Permissions.manage_users, "getPureUser")
396 def getPureUser(self, id, ):
397 """Return the named user object or None"""
398 # Performance tricks
399 if not id:
400 return None
401
402 # Fetch it
403 return self.getUser(id, __include_groups__ = 0)
404
405
406 security.declareProtected(Permissions.manage_users, "getGroupNames")
407 def getGroupNames(self, ):
408 """Same as getUserNames() but without pure users.
409 """
410 return self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 0)
411
412 security.declareProtected(Permissions.manage_users, "getGroupIds")
413 def getGroupIds(self, ):
414 """Same as getUserNames() but without pure users.
415 """
416 return self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 1)
417
418 security.declareProtected(Permissions.manage_users, "getGroups")
419 def getGroups(self):
420 """Same as getUsers() but without pure users.
421 """
422 return self.getUsers(__include_users__ = 0)
423
424 security.declareProtected(Permissions.manage_users, "getGroup")
425 def getGroup(self, name, prefixed = 1):
426 """Return the named user object or None"""
427 # Performance tricks
428 if not name:
429 return None
430
431 # Unprefix group name
432 if not name.startswith(GROUP_PREFIX):
433 name = "%s%s" % (GROUP_PREFIX, name, )
434
435 # Fetch it
436 return self.getUser(name, __include_users__ = 0)
437
438 security.declareProtected(Permissions.manage_users, "getGroupById")
439 def getGroupById(self, id, default = _marker):
440 """Same as getUserById(id) but forces returning a group.
441 """
442 ret = self.getUser(id, __include_users__ = 0, __force_group_id__ = 1)
443 if not ret:
444 if default is _marker:
445 return None
446 ret = default
447 return ret
448
449 security.declareProtected(Permissions.manage_users, "getGroupByName")
450 def getGroupByName(self, name, default = _marker):
451 """Same as getUserByName(name) but forces returning a group.
452 """
453 ret = self.getUser(name, __include_users__ = 0, __force_group_id__ = 0)
454 if not ret:
455 if default is _marker:
456 return None
457 ret = default
458 return ret
459
460
461
462 # #
463 # REGULAR MUTATORS #
464 # #
465
466 security.declareProtected(Permissions.manage_users, "userFolderAddUser")
467 def userFolderAddUser(self, name, password, roles, domains, groups = (),
468 REQUEST=None, **kw):
469 """API method for creating a new user object. Note that not all
470 user folder implementations support dynamic creation of user
471 objects.
472 """
473 return self._doAddUser(name, password, roles, domains, groups, **kw)
474 userFolderAddUser = postonly(userFolderAddUser)
475
476 security.declareProtected(Permissions.manage_users, "userFolderEditUser")
477 def userFolderEditUser(self, name, password, roles, domains, groups = None,
478 REQUEST=None, **kw):
479 """API method for changing user object attributes. Note that not
480 all user folder implementations support changing of user object
481 attributes.
482 Arguments ARE required.
483 """
484 return self._doChangeUser(name, password, roles, domains, groups, **kw)
485 userFolderEditUser = postonly(userFolderEditUser)
486
487 security.declareProtected(Permissions.manage_users, "userFolderUpdateUser")
488 def userFolderUpdateUser(self, name, password = None, roles = None,
489 domains = None, groups = None, REQUEST=None, **kw):
490 """API method for changing user object attributes. Note that not
491 all user folder implementations support changing of user object
492 attributes.
493 Arguments are optional"""
494 return self._updateUser(name, password, roles, domains, groups, **kw)
495 userFolderUpdateUser = postonly(userFolderUpdateUser)
496
497 security.declareProtected(Permissions.manage_users, "userFolderDelUsers")
498 def userFolderDelUsers(self, names, REQUEST=None):
499 """API method for deleting one or more user atom objects. Note that not
500 all user folder implementations support deletion of user objects."""
501 return self._doDelUsers(names)
502 userFolderDelUsers = postonly(userFolderDelUsers)
503
504 security.declareProtected(Permissions.manage_users, "userFolderAddGroup")
505 def userFolderAddGroup(self, name, roles, groups = (), REQUEST=None, **kw):
506 """API method for creating a new group.
507 """
508 while name.startswith(GROUP_PREFIX):
509 name = name[GROUP_PREFIX_LEN:]
510 return self._doAddGroup(name, roles, groups, **kw)
511 userFolderAddGroup = postonly(userFolderAddGroup)
512
513 security.declareProtected(Permissions.manage_users, "userFolderEditGroup")
514 def userFolderEditGroup(self, name, roles, groups = None, REQUEST=None,
515 **kw):
516 """API method for changing group object attributes.
517 """
518 return self._doChangeGroup(name, roles = roles, groups = groups, **kw)
519 userFolderEditGroup = postonly(userFolderEditGroup)
520
521 security.declareProtected(Permissions.manage_users, "userFolderUpdateGroup")
522 def userFolderUpdateGroup(self, name, roles = None, groups = None,
523 REQUEST=None, **kw):
524 """API method for changing group object attributes.
525 """
526 return self._updateGroup(name, roles = roles, groups = groups, **kw)
527 userFolderUpdateGroup = postonly(userFolderUpdateGroup)
528
529 security.declareProtected(Permissions.manage_users, "userFolderDelGroups")
530 def userFolderDelGroups(self, names, REQUEST=None):
531 """API method for deleting one or more group objects.
532 Implem. note : All ids must be prefixed with 'group_',
533 so this method ends up beeing only a filter of non-prefixed ids
534 before calling userFolderDelUsers().
535 """
536 return self._doDelGroups(names)
537 userFolderDelUsers = postonly(userFolderDelUsers)
538
539
540
541 # #
542 # SEARCH METHODS #
543 # #
544
545
546 security.declareProtected(Permissions.manage_users, "searchUsersByAttribute")
547 def searchUsersByAttribute(self, attribute, search_term):
548 """Return user ids whose 'attribute' match the specified search_term.
549 If search_term is an empty string, behaviour depends on the underlying user folder:
550 it may return all users, return only cached users (for LDAPUF) or return no users.
551 This will return all users whose name contains search_term (whaterver its case).
552 THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
553 SEARCHING METHOD (ie. every UF kind except LDAPUF).
554 'attribute' can be 'id' or 'name' for all UF kinds, or anything else for LDAPUF.
555 """
556 ret = []
557 for src in self.listUserSources():
558 # Use source-specific search methods if available
559 if hasattr(src.aq_base, "findUser"):
560 # LDAPUF
561 Log(LOG_DEBUG, "We use LDAPUF to find users")
562 id_attr = src._uid_attr
563 if attribute == 'name':
564 attr = src._login_attr
565 elif attribute == 'id':
566 attr = src._uid_attr
567 else:
568 attr = attribute
569 Log(LOG_DEBUG, "we use findUser", attr, search_term, )
570 users = src.findUser(attr, search_term, exact_match = True)
571 ret.extend(
572 [ u[id_attr] for u in users ],
573 )
574 else:
575 # Other types of user folder
576 search_term = search_term.lower()
577
578 # Find the proper method according to the attribute type
579 if attribute == "name":
580 method = "getName"
581 elif attribute == "id":
582 method = "getId"
583 else:
584 raise NotImplementedError, "Attribute searching is only supported for LDAPUserFolder by now."
585
586 # Actually search
587 src_id = src.getUserSourceId()
588 for u in src.getUsers():
589 if not u:
590 continue
591 u = GRUFUser.GRUFUser(u, self, source_id=src_id,
592 isGroup=0).__of__(self)
593 s = getattr(u, method)().lower()
594 if string.find(s, search_term) != -1:
595 ret.append(u.getId())
596 Log(LOG_DEBUG, "We've found them:", ret)
597 return ret
598
599 security.declareProtected(Permissions.manage_users, "searchUsersByName")
600 def searchUsersByName(self, search_term):
601 """Return user ids whose name match the specified search_term.
602 If search_term is an empty string, behaviour depends on the underlying user folder:
603 it may return all users, return only cached users (for LDAPUF) or return no users.
604 This will return all users whose name contains search_term (whaterver its case).
605 THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
606 SEARCHING METHOD (ie. every UF kind except LDAPUF)
607 """
608 return self.searchUsersByAttribute("name", search_term)
609
610 security.declareProtected(Permissions.manage_users, "searchUsersById")
611 def searchUsersById(self, search_term):
612 """Return user ids whose id match the specified search_term.
613 If search_term is an empty string, behaviour depends on the underlying user folder:
614 it may return all users, return only cached users (for LDAPUF) or return no users.
615 This will return all users whose name contains search_term (whaterver its case).
616 THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
617 SEARCHING METHOD (ie. every UF kind except LDAPUF)
618 """
619 return self.searchUsersByAttribute("id", search_term)
620
621
622 security.declareProtected(Permissions.manage_users, "searchGroupsByAttribute")
623 def searchGroupsByAttribute(self, attribute, search_term):
624 """Return group ids whose 'attribute' match the specified search_term.
625 If search_term is an empty string, behaviour depends on the underlying group folder:
626 it may return all groups, return only cached groups (for LDAPUF) or return no groups.
627 This will return all groups whose name contains search_term (whaterver its case).
628 THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
629 SEARCHING METHOD (ie. every UF kind except LDAPUF).
630 'attribute' can be 'id' or 'name' for all UF kinds, or anything else for LDAPUF.
631 """
632 ret = []
633 src = self.Groups
634
635 # Use source-specific search methods if available
636 if hasattr(src.aq_base, "findGroup"):
637 # LDAPUF
638 id_attr = src._uid_attr
639 if attribute == 'name':
640 attr = src._login_attr
641 elif attribute == 'id':
642 attr = src._uid_attr
643 else:
644 attr = attribute
645 groups = src.findGroup(attr, search_term)
646 ret.extend(
647 [ u[id_attr] for u in groups ],
648 )
649 else:
650 # Other types of group folder
651 search_term = search_term.lower()
652
653 # Find the proper method according to the attribute type
654 if attribute == "name":
655 method = "getName"
656 elif attribute == "id":
657 method = "getId"
658 else:
659 raise NotImplementedError, "Attribute searching is only supported for LDAPGroupFolder by now."
660
661 # Actually search
662 for u in self.getGroups():
663 s = getattr(u, method)().lower()
664 if string.find(s, search_term) != -1:
665 ret.append(u.getId())
666 return ret
667
668 security.declareProtected(Permissions.manage_users, "searchGroupsByName")
669 def searchGroupsByName(self, search_term):
670 """Return group ids whose name match the specified search_term.
671 If search_term is an empty string, behaviour depends on the underlying group folder:
672 it may return all groups, return only cached groups (for LDAPUF) or return no groups.
673 This will return all groups whose name contains search_term (whaterver its case).
674 THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
675 SEARCHING METHOD (ie. every UF kind except LDAPUF)
676 """
677 return self.searchGroupsByAttribute("name", search_term)
678
679 security.declareProtected(Permissions.manage_users, "searchGroupsById")
680 def searchGroupsById(self, search_term):
681 """Return group ids whose id match the specified search_term.
682 If search_term is an empty string, behaviour depends on the underlying group folder:
683 it may return all groups, return only cached groups (for LDAPUF) or return no groups.
684 This will return all groups whose name contains search_term (whaterver its case).
685 THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
686 SEARCHING METHOD (ie. every UF kind except LDAPUF)
687 """
688 return self.searchGroupsByAttribute("id", search_term)
689
690 # #
691 # SECURITY MANAGEMENT METHODS #
692 # #
693
694 security.declareProtected(Permissions.manage_users, "setRolesOnUsers")
695 def setRolesOnUsers(self, roles, userids, REQUEST = None):
696 """Set a common set of roles for a bunch of user atoms.
697 """
698 for usr in userids:
699 self.userSetRoles(usr, roles)
700 setRolesOnUsers = postonly(setRolesOnUsers)
701
702 ## def setUsersOfRole(self, usernames, role):
703 ## """Sets the users of a role.
704 ## XXX THIS METHOD SEEMS TO BE SEAMLESS.
705 ## """
706 ## raise NotImplementedError, "Not implemented."
707
708 security.declareProtected(Permissions.manage_users, "getUsersOfRole")
709 def getUsersOfRole(self, role, object = None):
710 """Gets the user (and group) ids having the specified role...
711 ...on the specified Zope object if it's not None
712 ...on their own information if the object is None.
713 NOTA: THIS METHOD IS VERY EXPENSIVE.
714 XXX PERFORMANCES HAVE TO BE IMPROVED
715 """
716 ret = []
717 for id in self.getUserIds():
718 if role in self.getRolesOfUser(id):
719 ret.append(id)
720 return tuple(ret)
721
722 security.declarePublic("getRolesOfUser")
723 def getRolesOfUser(self, userid):
724 """Alias for user.getRoles()
725 """
726 return self.getUserById(userid).getRoles()
727
728 security.declareProtected(Permissions.manage_users, "userFolderAddRole")
729 def userFolderAddRole(self, role, REQUEST=None):
730 """Add a new role. The role will be appended, in fact, in GRUF's surrounding folder.
731 """
732 if role in self.aq_parent.valid_roles():
733 raise ValueError, "Role '%s' already exist" % (role, )
734
735 return self.aq_parent._addRole(role)
736 userFolderAddRole = postonly(userFolderAddRole)
737
738 security.declareProtected(Permissions.manage_users, "userFolderDelRoles")
739 def userFolderDelRoles(self, roles, REQUEST=None):
740 """Delete roles.
741 The removed roles will be removed from the UserFolder's users and groups as well,
742 so this method can be very time consuming with a large number of users.
743 """
744 # Check that roles exist
745 ud_roles = self.aq_parent.userdefined_roles()
746 for r in roles:
747 if not r in ud_roles:
748 raise ValueError, "Role '%s' is not defined on acl_users' parent folder" % (r, )
749
750 # Remove role on all users
751 for r in roles:
752 for u in self.getUsersOfRole(r, ):
753 self.userRemoveRole(u, r, )
754
755 # Actually remove role
756 return self.aq_parent._delRoles(roles, None)
757 userFolderDelRoles = postonly(userFolderDelRoles)
758
759 security.declarePublic("userFolderGetRoles")
760 def userFolderGetRoles(self, ):
761 """
762 userFolderGetRoles(self,) => tuple of strings
763 List the roles defined at the top of GRUF's folder.
764 This includes both user-defined roles and default roles.
765 """
766 return tuple(self.aq_parent.valid_roles())
767
768
769 # Groups support
770
771 security.declareProtected(Permissions.manage_users, "setMembers")
772 def setMembers(self, groupid, userids, REQUEST=None):
773 """Set the members of the group
774 """
775 self.getGroup(groupid).setMembers(userids)
776 setMembers = postonly(setMembers)
777
778 security.declareProtected(Permissions.manage_users, "addMember")
779 def addMember(self, groupid, userid, REQUEST=None):
780 """Add a member to a group
781 """
782 return self.getGroup(groupid).addMember(userid)
783 addMember = postonly(addMember)
784
785 security.declareProtected(Permissions.manage_users, "removeMember")
786 def removeMember(self, groupid, userid, REQUEST=None):
787 """Remove a member from a group.
788 """
789 return self.getGroup(groupid).removeMember(userid)
790 removeMember = postonly(removeMember)
791
792 security.declareProtected(Permissions.manage_users, "getMemberIds")
793 def getMemberIds(self, groupid):
794 """Return the list of member ids (groups and users) in this group
795 """
796 m = self.getGroup(groupid)
797 if not m:
798 raise ValueError, "Invalid group: '%s'" % groupid
799 return self.getGroup(groupid).getMemberIds()
800
801 security.declareProtected(Permissions.manage_users, "getUserMemberIds")
802 def getUserMemberIds(self, groupid):
803 """Return the list of member ids (groups and users) in this group
804 """
805 return self.getGroup(groupid).getUserMemberIds()
806
807 security.declareProtected(Permissions.manage_users, "getGroupMemberIds")
808 def getGroupMemberIds(self, groupid):
809 """Return the list of member ids (groups and users) in this group
810 XXX THIS MAY BE VERY EXPENSIVE !
811 """
812 return self.getGroup(groupid).getGroupMemberIds()
813
814 security.declareProtected(Permissions.manage_users, "hasMember")
815 def hasMember(self, groupid, id):
816 """Return true if the specified atom id is in the group.
817 This is the contrary of IUserAtom.isInGroup(groupid).
818 THIS CAN BE VERY EXPENSIVE
819 """
820 return self.getGroup(groupid).hasMember(id)
821
822
823 # User mutation
824
825 ## def setUserId(id, newId):
826 ## """Change id of a user atom.
827 ## """
828
829 ## def setUserName(id, newName):
830 ## """Change the name of a user atom.
831 ## """
832
833 security.declareProtected(Permissions.manage_users, "userSetRoles")
834 def userSetRoles(self, id, roles, REQUEST=None):
835 """Change the roles of a user atom.
836 """
837 self._updateUser(id, roles = roles)
838 userSetRoles = postonly(userSetRoles)
839
840 security.declareProtected(Permissions.manage_users, "userAddRole")
841 def userAddRole(self, id, role, REQUEST=None):
842 """Append a role for a user atom
843 """
844 roles = list(self.getUser(id).getRoles())
845 if not role in roles:
846 roles.append(role)
847 self._updateUser(id, roles = roles)
848 userAddRole = postonly(userAddRole)
849
850 security.declareProtected(Permissions.manage_users, "userRemoveRole")
851 def userRemoveRole(self, id, role, REQUEST=None):
852 """Remove the role of a user atom. Will NOT complain if role doesn't exist
853 """
854 roles = list(self.getRolesOfUser(id))
855 if role in roles:
856 roles.remove(role)
857 self._updateUser(id, roles = roles)
858 userRemoveRole = postonly(userRemoveRole)
859
860 security.declareProtected(Permissions.manage_users, "userSetPassword")
861 def userSetPassword(self, id, newPassword, REQUEST=None):
862 """Set the password of a user
863 """
864 u = self.getPureUser(id)
865 if not u:
866 raise ValueError, "Invalid pure user id: '%s'" % (id,)
867 self._updateUser(u.getId(), password = newPassword, )
868 userSetPassword = postonly(userSetPassword)
869
870 security.declareProtected(Permissions.manage_users, "userGetDomains")
871 def userGetDomains(self, id):
872 """get domains for a user
873 """
874 usr = self.getPureUser(id)
875 return tuple(usr.getDomains())
876
877 security.declareProtected(Permissions.manage_users, "userSetDomains")
878 def userSetDomains(self, id, domains, REQUEST=None):
879 """Set domains for a user
880 """
881 usr = self.getPureUser(id)
882 self._updateUser(usr.getId(), domains = domains, )
883 userSetDomains = postonly(userSetDomains)
884
885 security.declareProtected(Permissions.manage_users, "userAddDomain")
886 def userAddDomain(self, id, domain, REQUEST=None):
887 """Append a domain to a user
888 """
889 usr = self.getPureUser(id)
890 domains = list(usr.getDomains())
891 if not domain in domains:
892 roles.append(domain)
893 self._updateUser(usr.getId(), domains = domains, )
894 userAddDomain = postonly(userAddDomain)
895
896 security.declareProtected(Permissions.manage_users, "userRemoveDomain")
897 def userRemoveDomain(self, id, domain, REQUEST=None):
898 """Remove a domain from a user
899 """
900 usr = self.getPureUser(id)
901 domains = list(usr.getDomains())
902 if not domain in domains:
903 raise ValueError, "User '%s' doesn't have domain '%s'" % (id, domain, )
904 while domain in domains:
905 roles.remove(domain)
906 self._updateUser(usr.getId(), domains = domains)
907 userRemoveDomain = postonly(userRemoveDomain)
908
909 security.declareProtected(Permissions.manage_users, "userSetGroups")
910 def userSetGroups(self, id, groupnames, REQUEST=None):
911 """Set the groups of a user
912 """
913 self._updateUser(id, groups = groupnames)
914 userSetGroups = postonly(userSetGroups)
915
916 security.declareProtected(Permissions.manage_users, "userAddGroup")
917 def userAddGroup(self, id, groupname, REQUEST=None):
918 """add a group to a user atom
919 """
920 groups = list(self.getUserById(id).getGroups())
921 if not groupname in groups:
922 groups.append(groupname)
923 self._updateUser(id, groups = groups)
924 userAddGroup = postonly(userAddGroup)
925
926
927 security.declareProtected(Permissions.manage_users, "userRemoveGroup")
928 def userRemoveGroup(self, id, groupname, REQUEST=None):
929 """remove a group from a user atom.
930 """
931 groups = list(self.getUserById(id).getGroupNames())
932 if groupname.startswith(GROUP_PREFIX):
933 groupname = groupname[GROUP_PREFIX_LEN:]
934 if groupname in groups:
935 groups.remove(groupname)
936 self._updateUser(id, groups = groups)
937 userRemoveGroup = postonly(userRemoveGroup)
938
939
940 # #
941 # VARIOUS OPERATIONS #
942 # #
943
944 def __init__(self):
945 """
946 __init__(self) -> initialization method
947 We define it to prevend calling ancestor's __init__ methods.
948 """
949 pass
950
951
952 security.declarePrivate('_post_init')
953 def _post_init(self):
954 """
955 _post_init(self) => meant to be called when the
956 object is in the Zope tree
957 """
958 uf = GRUFFolder.GRUFUsers()
959 gf = GRUFFolder.GRUFGroups()
960 self._setObject('Users', uf)
961 self._setObject('Groups', gf)
962 self.id = "acl_users"
963
964 def manage_beforeDelete(self, item, container):
965 """
966 Special overloading for __allow_groups__ attribute
967 """
968 if item is self:
969 try:
970 del container.__allow_groups__
971 except:
972 pass
973
974 def manage_afterAdd(self, item, container):
975 """Same
976 """
977 if item is self:
978 container.__allow_groups__ = aq_base(self)
979
980 # #
981 # VARIOUS UTILITIES #
982 # #
983 # These methods shouldn't be used directly for most applications, #
984 # but they might be useful for some special processing. #
985 # #
986
987 security.declarePublic('getGroupPrefix')
988 def getGroupPrefix(self):
989 """ group prefix """
990 return GROUP_PREFIX
991
992 security.declarePrivate('getGRUFPhysicalRoot')
993 def getGRUFPhysicalRoot(self,):
994 # $$$ trick meant to be used within
995 # fake_getPhysicalRoot (see __init__)
996 return self.getPhysicalRoot()
997
998 security.declareProtected(Permissions.view, 'getGRUFId')
999 def getGRUFId(self,):
1000 """
1001 Alias to self.getId()
1002 """
1003 return self.getId()
1004
1005 security.declareProtected(Permissions.manage_users, "getUnwrappedUser")
1006 def getUnwrappedUser(self, name):
1007 """
1008 getUnwrappedUser(self, name) => user object or None
1009
1010 This method is used to get a User object directly from the User's
1011 folder acl_users, without wrapping it with group information.
1012
1013 This is useful for UserFolders that define additional User classes,
1014 when you want to call specific methods on these user objects.
1015
1016 For example, LDAPUserFolder defines a 'getProperty' method that's
1017 not inherited from the standard User object. You can, then, use
1018 the getUnwrappedUser() to get the matching user and call this
1019 method.
1020 """
1021 src_id = self.getUser(name).getUserSourceId()
1022 return self.getUserSource(src_id).getUser(name)
1023
1024 security.declareProtected(Permissions.manage_users, "getUnwrappedGroup")
1025 def getUnwrappedGroup(self, name):
1026 """
1027 getUnwrappedGroup(self, name) => user object or None
1028
1029 Same as getUnwrappedUser but for groups.
1030 """
1031 return self.Groups.acl_users.getUser(name)
1032
1033 # #
1034 # AUTHENTICATION INTERFACE #
1035 # #
1036
1037 security.declarePrivate("authenticate")
1038 def authenticate(self, name, password, request):
1039 """
1040 Pass the request along to the underlying user-related UserFolder
1041 object
1042 THIS METHOD RETURNS A USER OBJECT OR NONE, as specified in the code
1043 in AccessControl/User.py.
1044 We also check for inituser in there.
1045 """
1046 # Emergency user checking stuff
1047 emergency = self._emergency_user
1048 if emergency and name == emergency.getUserName():
1049 if emergency.authenticate(password, request):
1050 return emergency
1051 else:
1052 return None
1053
1054 # Usual GRUF authentication
1055 for src in self.listUserSources():
1056 # XXX We can imagine putting a try/except here to "ignore"
1057 # UF errors such as SQL or LDAP shutdown
1058 u = src.authenticate(name, password, request)
1059 if u:
1060 return GRUFUser.GRUFUser(u, self, isGroup = 0, source_id = src.getUserSourceId()).__of__(self)
1061
1062 # No acl_users in the Users folder or no user authenticated
1063 # => we refuse authentication
1064 return None
1065
1066
1067
1068
1069 # #
1070 # GRUF'S GUTS :-) #
1071 # #
1072
1073 security.declarePrivate("_doAddUser")
1074 def _doAddUser(self, name, password, roles, domains, groups = (), **kw):
1075 """
1076 Create a new user. This should be implemented by subclasses to
1077 do the actual adding of a user. The 'password' will be the
1078 original input password, unencrypted. The implementation of this
1079 method is responsible for performing any needed encryption.
1080 """
1081 prefix = GROUP_PREFIX
1082
1083 # Prepare groups
1084 roles = list(roles)
1085 gruf_groups = self.getGroupIds()
1086 for group in groups:
1087 if not group.startswith(prefix):
1088 group = "%s%s" % (prefix, group, )
1089 if not group in gruf_groups:
1090 raise ValueError, "Invalid group: '%s'" % (group, )
1091 roles.append(group)
1092
1093 # Reset the users overview batch
1094 self._v_batch_users = []
1095
1096 # Really add users
1097 return self.getDefaultUserSource()._doAddUser(
1098 name,
1099 password,
1100 roles,
1101 domains,
1102 **kw)
1103
1104 security.declarePrivate("_doChangeUser")
1105 def _doChangeUser(self, name, password, roles, domains, groups = None, **kw):
1106 """
1107 Modify an existing user. This should be implemented by subclasses
1108 to make the actual changes to a user. The 'password' will be the
1109 original input password, unencrypted. The implementation of this
1110 method is responsible for performing any needed encryption.
1111
1112 A None password should not change it (well, we hope so)
1113 """
1114 # Get actual user name and id
1115 usr = self.getUser(name)
1116 if usr is None:
1117 raise ValueError, "Invalid user: '%s'" % (name,)
1118 id = usr.getRealId()
1119
1120 # Don't lose existing groups
1121 if groups is None:
1122 groups = usr.getGroups()
1123
1124 roles = list(roles)
1125 groups = list(groups)
1126
1127 # Change groups affectation
1128 cur_groups = self.getGroups()
1129 given_roles = tuple(usr.getRoles()) + tuple(roles)
1130 for group in groups:
1131 if not group.startswith(GROUP_PREFIX, ):
1132 group = "%s%s" % (GROUP_PREFIX, group, )
1133 if not group in cur_groups and not group in given_roles:
1134 roles.append(group)
1135
1136 # Reset the users overview batch
1137 self._v_batch_users = []
1138
1139 # Change the user itself
1140 src = usr.getUserSourceId()
1141 Log(LOG_NOTICE, name, "Source:", src)
1142 ret = self.getUserSource(src)._doChangeUser(
1143 id, password, roles, domains, **kw)
1144
1145 # Invalidate user cache if necessary
1146 usr.clearCachedGroupsAndRoles()
1147 authenticated = getSecurityManager().getUser()
1148 if id == authenticated.getId() and hasattr(authenticated, 'clearCachedGroupsAndRoles'):
1149 authenticated.clearCachedGroupsAndRoles(self.getUserSource(src).getUser(id))
1150
1151 return ret
1152
1153 security.declarePrivate("_updateUser")
1154 def _updateUser(self, id, password = None, roles = None, domains = None, groups = None):
1155 """
1156 _updateUser(self, id, password = None, roles = None, domains = None, groups = None)
1157
1158 This one should work for users AND groups.
1159
1160 Front-end to _doChangeUser, but with a better default value support.
1161 We guarantee that None values will let the underlying UF keep the original ones.
1162 This is not true for the password: some buggy UF implementation may not
1163 handle None password correctly :-(
1164 """
1165 # Get the former values if necessary. Username must be valid !
1166 usr = self.getUser(id)
1167 if roles is None:
1168 # Remove invalid roles and group names
1169 roles = usr._original_roles
1170 roles = filter(lambda x: not x.startswith(GROUP_PREFIX), roles)
1171 roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared', ''), roles)
1172 else:
1173 # Check if roles are valid
1174 roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared', ''), roles)
1175 vr = self.userFolderGetRoles()
1176 for r in roles:
1177 if not r in vr:
1178 raise ValueError, "Invalid or inexistant role: '%s'." % (r, )
1179 if domains is None:
1180 domains = usr._original_domains
1181 if groups is None:
1182 groups = usr.getGroups(no_recurse = 1)
1183 else:
1184 # Check if given groups are valid
1185 glist = self.getGroupNames()
1186 glist.extend(map(lambda x: "%s%s" % (GROUP_PREFIX, x), glist))
1187 for g in groups:
1188 if not g in glist:
1189 raise ValueError, "Invalid group: '%s'" % (g, )
1190
1191 # Reset the users overview batch
1192 self._v_batch_users = []
1193
1194 # Change the user
1195 return self._doChangeUser(id, password, roles, domains, groups)
1196
1197 security.declarePrivate("_doDelUsers")
1198 def _doDelUsers(self, names):
1199 """
1200 Delete one or more users. This should be implemented by subclasses
1201 to do the actual deleting of users.
1202 This won't delete groups !
1203 """
1204 # Collect information about user sources
1205 sources = {}
1206 for name in names:
1207 usr = self.getUser(name, __include_groups__ = 0)
1208 if not usr:
1209 continue # Ignore invalid user names
1210 src = usr.getUserSourceId()
1211 if not sources.has_key(src):
1212 sources[src] = []
1213 sources[src].append(name)
1214 for src, names in sources.items():
1215 self.getUserSource(src)._doDelUsers(names)
1216
1217 # Reset the users overview batch
1218 self._v_batch_users = []
1219
1220
1221 # #
1222 # Groups interface #
1223 # #
1224
1225 security.declarePrivate("_doAddGroup")
1226 def _doAddGroup(self, name, roles, groups = (), **kw):
1227 """
1228 Create a new group. Password will be randomly created, and domain will be None.
1229 Supports nested groups.
1230 """
1231 # Prepare initial data
1232 domains = ()
1233 password = ""
1234 if roles is None:
1235 roles = []
1236 if groups is None:
1237 groups = []
1238
1239 for x in range(0, 10): # Password will be 10 chars long
1240 password = "%s%s" % (password, random.choice(string.lowercase), )
1241
1242 # Compute roles
1243 roles = list(roles)
1244 prefix = GROUP_PREFIX
1245 gruf_groups = self.getGroupIds()
1246 for group in groups:
1247 if not group.startswith(prefix):
1248 group = "%s%s" % (prefix, group, )
1249 if group == "%s%s" % (prefix, name, ):
1250 raise ValueError, "Infinite recursion for group '%s'." % (group, )
1251 if not group in gruf_groups:
1252 raise ValueError, "Invalid group: '%s' (defined groups are %s)" % (group, gruf_groups)
1253 roles.append(group)
1254
1255 # Reset the users overview batch
1256 self._v_batch_users = []
1257
1258 # Actual creation
1259 return self.Groups.acl_users._doAddUser(
1260 name, password, roles, domains, **kw
1261 )
1262
1263 security.declarePrivate("_doChangeGroup")
1264 def _doChangeGroup(self, name, roles, groups = None, **kw):
1265 """Modify an existing group."""
1266 # Remove prefix if given
1267 if name.startswith(self.getGroupPrefix()):
1268 name = name[GROUP_PREFIX_LEN:]
1269
1270 # Check if group exists
1271 grp = self.getGroup(name, prefixed = 0)
1272 if grp is None:
1273 raise ValueError, "Invalid group: '%s'" % (name,)
1274
1275 # Don't lose existing groups
1276 if groups is None:
1277 groups = grp.getGroups()
1278
1279 roles = list(roles or [])
1280 groups = list(groups or [])
1281
1282 # Change groups affectation
1283 cur_groups = self.getGroups()
1284 given_roles = tuple(grp.getRoles()) + tuple(roles)
1285 for group in groups:
1286 if not group.startswith(GROUP_PREFIX, ):
1287 group = "%s%s" % (GROUP_PREFIX, group, )
1288 if group == "%s%s" % (GROUP_PREFIX, grp.id):
1289 raise ValueError, "Cannot affect group '%s' to itself!" % (name, ) # Prevent direct inclusion of self
1290 new_grp = self.getGroup(group)
1291 if not new_grp:
1292 raise ValueError, "Invalid or inexistant group: '%s'" % (group, )
1293 if "%s%s" % (GROUP_PREFIX, grp.id) in new_grp.getGroups():
1294 raise ValueError, "Cannot affect %s to group '%s' as it would lead to circular references." % (group, name, ) # Prevent indirect inclusion of self
1295 if not group in cur_groups and not group in given_roles:
1296 roles.append(group)
1297
1298 # Reset the users overview batch
1299 self._v_batch_users = []
1300
1301 # Perform the change
1302 domains = ""
1303 password = ""
1304 for x in range(0, 10): # Password will be 10 chars long
1305 password = "%s%s" % (password, random.choice(string.lowercase), )
1306 return self.Groups.acl_users._doChangeUser(name, password,
1307 roles, domains, **kw)
1308
1309 security.declarePrivate("_updateGroup")
1310 def _updateGroup(self, name, roles = None, groups = None):
1311 """
1312 _updateGroup(self, name, roles = None, groups = None)
1313
1314 Front-end to _doChangeUser, but with a better default value support.
1315 We guarantee that None values will let the underlying UF keep the original ones.
1316 This is not true for the password: some buggy UF implementation may not
1317 handle None password correctly but we do not care for Groups.
1318
1319 group name can be prefixed or not
1320 """
1321 # Remove prefix if given
1322 if name.startswith(self.getGroupPrefix()):
1323 name = name[GROUP_PREFIX_LEN:]
1324
1325 # Get the former values if necessary. Username must be valid !
1326 usr = self.getGroup(name, prefixed = 0)
1327 if roles is None:
1328 # Remove invalid roles and group names
1329 roles = usr._original_roles
1330 roles = filter(lambda x: not x.startswith(GROUP_PREFIX), roles)
1331 roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared'), roles)
1332 if groups is None:
1333 groups = usr.getGroups(no_recurse = 1)
1334
1335 # Reset the users overview batch
1336 self._v_batch_users = []
1337
1338 # Change the user
1339 return self._doChangeGroup(name, roles, groups)
1340
1341
1342 security.declarePrivate("_doDelGroup")
1343 def _doDelGroup(self, name):
1344 """Delete one user."""
1345 # Remove prefix if given
1346 if name.startswith(self.getGroupPrefix()):
1347 name = name[GROUP_PREFIX_LEN:]
1348
1349 # Reset the users overview batch
1350 self._v_batch_users = []
1351
1352 # Delete it
1353 return self.Groups.acl_users._doDelUsers([name])
1354
1355 security.declarePrivate("_doDelGroups")
1356 def _doDelGroups(self, names):
1357 """Delete one or more users."""
1358 for group in names:
1359 if not self.getGroupByName(group, None):
1360 continue # Ignore invalid groups
1361 self._doDelGroup(group)
1362
1363
1364
1365
1366 # #
1367 # Pretty Management form methods #
1368 # #
1369
1370
1371 security.declarePublic('getGRUFVersion')
1372 def getGRUFVersion(self,):
1373 """
1374 getGRUFVersion(self,) => Return human-readable GRUF version as a string.
1375 """
1376 rev_date = "$Date: 2007-04-01 17:13:44 +0200 (dim, 01 avr 2007) $"[7:-2]
1377 return "%s / Revised %s" % (version__, rev_date)
1378
1379
1380 reset_entry = "__None__" # Special entry used for reset
1381
1382 security.declareProtected(Permissions.manage_users, "changeUser")
1383 def changeUser(self, user, groups = [], roles = [], REQUEST = {}, ):
1384 """
1385 changeUser(self, user, groups = [], roles = [], REQUEST = {}, ) => used in ZMI
1386 """
1387 obj = self.getUser(user)
1388 if obj.isGroup():
1389 self._updateGroup(name = user, groups = groups, roles = roles, )
1390 else:
1391 self._updateUser(id = user, groups = groups, roles = roles, )
1392
1393
1394 if REQUEST.has_key('RESPONSE'):
1395 return REQUEST.RESPONSE.redirect(self.absolute_url() + "/" + obj.getId() + "/manage_workspace?FORCE_USER=1")
1396 changeUser = postonly(changeUser)
1397
1398 security.declareProtected(Permissions.manage_users, "deleteUser")
1399 def deleteUser(self, user, REQUEST = {}, ):
1400 """
1401 deleteUser(self, user, REQUEST = {}, ) => used in ZMI
1402 """
1403 pass
1404 deleteUser = postonly(deleteUser)
1405
1406 security.declareProtected(Permissions.manage_users, "changeOrCreateUsers")
1407 def changeOrCreateUsers(self, users = [], groups = [], roles = [], new_users = [], default_password = '', REQUEST = {}, ):
1408 """
1409 changeOrCreateUsers => affect roles & groups to users and/or create new users
1410
1411 All parameters are strings or lists (NOT tuples !).
1412 NO CHECKING IS DONE. This is an utility method, it's not part of the official API.
1413 """
1414 # Manage roles / groups deletion
1415 del_roles = 0
1416 del_groups = 0
1417 if self.reset_entry in roles:
1418 roles.remove(self.reset_entry)
1419 del_roles = 1
1420 if self.reset_entry in groups:
1421 groups.remove(self.reset_entry)
1422 del_groups = 1
1423 if not roles and not del_roles:
1424 roles = None # None instead of [] to avoid deletion
1425 add_roles = []
1426 else:
1427 add_roles = roles
1428 if not groups and not del_groups:
1429 groups = None
1430 add_groups = []
1431 else:
1432 add_groups = groups
1433
1434 # Passwords management
1435 passwords_list = []
1436
1437 # Create brand new users
1438 for new in new_users:
1439 # Strip name
1440 name = string.strip(new)
1441 if not name:
1442 continue
1443
1444 # Avoid erasing former users
1445 if name in map(lambda x: x.getId(), self.getUsers()):
1446 continue
1447
1448 # Use default password or generate a random one
1449 if default_password:
1450 password = default_password
1451 else:
1452 password = ""
1453 for x in range(0, 8): # Password will be 8 chars long
1454 password = "%s%s" % (password, random.choice("ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"), )
1455 self._doAddUser(name, password, add_roles, (), add_groups, )
1456
1457 # Store the newly created password
1458 passwords_list.append({'name':name, 'password':password})
1459
1460 # Update existing users
1461 for user in users:
1462 self._updateUser(id = user, groups = groups, roles = roles, )
1463
1464 # Web request
1465 if REQUEST.has_key('RESPONSE'):
1466 # Redirect if no users have been created
1467 if not passwords_list:
1468 return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_users")
1469
1470 # Show passwords form
1471 else:
1472 REQUEST.set('USER_PASSWORDS', passwords_list)
1473 return self.manage_newusers(None, self)
1474
1475 # Simply return the list of created passwords
1476 return passwords_list
1477 changeOrCreateUsers = postonly(changeOrCreateUsers)
1478
1479 security.declareProtected(Permissions.manage_users, "deleteUsers")
1480 def deleteUsers(self, users = [], REQUEST = {}):
1481 """
1482 deleteUsers => explicit
1483
1484 All parameters are strings. NO CHECKING IS DONE. This is an utility method !
1485 """
1486 # Delete them
1487 self._doDelUsers(users, )
1488
1489 # Redirect
1490 if REQUEST.has_key('RESPONSE'):
1491 return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_users")
1492 deleteUsers = postonly(deleteUsers)
1493
1494 security.declareProtected(Permissions.manage_users, "changeOrCreateGroups")
1495 def changeOrCreateGroups(self, groups = [], roles = [], nested_groups = [], new_groups = [], REQUEST = {}, ):
1496 """
1497 changeOrCreateGroups => affect roles to groups and/or create new groups
1498
1499 All parameters are strings. NO CHECKING IS DONE. This is an utility method !
1500 """
1501 # Manage roles / groups deletion
1502 del_roles = 0
1503 del_groups = 0
1504 if self.reset_entry in roles:
1505 roles.remove(self.reset_entry)
1506 del_roles = 1
1507 if self.reset_entry in nested_groups:
1508 nested_groups.remove(self.reset_entry)
1509 del_groups = 1
1510 if not roles and not del_roles:
1511 roles = None # None instead of [] to avoid deletion
1512 add_roles = []
1513 else:
1514 add_roles = roles
1515 if not nested_groups and not del_groups:
1516 nested_groups = None
1517 add_groups = []
1518 else:
1519 add_groups = nested_groups
1520
1521 # Create brand new groups
1522 for new in new_groups:
1523 name = string.strip(new)
1524 if not name:
1525 continue
1526 self._doAddGroup(name, roles, groups = add_groups)
1527
1528 # Update existing groups
1529 for group in groups:
1530 self._updateGroup(group, roles = roles, groups = nested_groups)
1531
1532 # Redirect
1533 if REQUEST.has_key('RESPONSE'):
1534 return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_groups")
1535 changeOrCreateGroups = postonly(changeOrCreateGroups)
1536
1537 security.declareProtected(Permissions.manage_users, "deleteGroups")
1538 def deleteGroups(self, groups = [], REQUEST = {}):
1539 """
1540 deleteGroups => explicit
1541
1542 All parameters are strings. NO CHECKING IS DONE. This is an utility method !
1543 """
1544 # Delete groups
1545 for group in groups:
1546 self._doDelGroup(group, )
1547
1548 # Redirect
1549 if REQUEST.has_key('RESPONSE'):
1550 return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_groups")
1551 deleteGroups = postonly(deleteGroups)
1552
1553 # #
1554 # Local Roles Acquisition Blocking #
1555 # Those two methods perform their own security check. #
1556 # #
1557
1558 security.declarePublic("acquireLocalRoles")
1559 def acquireLocalRoles(self, folder, status, REQUEST=None):
1560 """
1561 Enable or disable local role acquisition on the specified folder.
1562 If status is true, it will enable, else it will disable.
1563 Note that the user _must_ have the change_permissions permission on the
1564 folder to allow changes on it.
1565 If you want to use this code from a product, please use _acquireLocalRoles()
1566 instead: this private method won't check security on the destination folder.
1567 It's usually a bad idea to use _acquireLocalRoles() directly in your product,
1568 but, well, after all, you do what you want ! :^)
1569 """
1570 # Perform security check on destination folder
1571 if not getSecurityManager().checkPermission(Permissions.change_permissions, folder):
1572 raise Unauthorized(name = "acquireLocalRoles")
1573
1574 return self._acquireLocalRoles(folder, status)
1575 acquireLocalRoles = postonly(acquireLocalRoles)
1576
1577 def _acquireLocalRoles(self, folder, status):
1578 """Same as _acquireLocalRoles() but won't perform security check on the folder.
1579 """
1580 # Set the variable (or unset it if it's defined)
1581 if not status:
1582 folder.__ac_local_roles_block__ = 1
1583 else:
1584 if getattr(folder, '__ac_local_roles_block__', None):
1585 folder.__ac_local_roles_block__ = None
1586
1587
1588 security.declarePublic("isLocalRoleAcquired")
1589 def isLocalRoleAcquired(self, folder):
1590 """Return true if the specified folder allows local role acquisition.
1591 """
1592 if getattr(folder, '__ac_local_roles_block__', None):
1593 return 0
1594 return 1
1595
1596
1597 # #
1598 # Security audit and info methods #
1599 # #
1600
1601
1602 # This method normally has NOT to be public ! It is because of a CMF inconsistancy.
1603 # folder_localrole_form is accessible to users who have the manage_properties permissions
1604 # (according to portal_types/Folder/Actions information). This is silly !
1605 # folder_localrole_form should be, in CMF, accessible only to those who have the
1606 # manage_users permissions instead of manage_properties permissions.
1607 # This is yet another one CMF bug we have to care about.
1608 # To deal with that in Plone2.1, we check for a particular permission on the destination
1609 # object _inside_ the method.
1610 security.declarePublic("getLocalRolesForDisplay")
1611 def getLocalRolesForDisplay(self, object):
1612 """This is used for plone's local roles display
1613 This method returns a tuple (massagedUsername, roles, userType, actualUserName).
1614 This method is protected by the 'Manage properties' permission. We may
1615 change that if it's too permissive..."""
1616 # Perform security check on destination object
1617 if not getSecurityManager().checkPermission(Permissions.manage_properties, object):
1618 raise Unauthorized(name = "getLocalRolesForDisplay")
1619
1620 return self._getLocalRolesForDisplay(object)
1621
1622 def _getLocalRolesForDisplay(self, object):
1623 """This is used for plone's local roles display
1624 This method returns a tuple (massagedUsername, roles, userType, actualUserName)"""
1625 result = []
1626 local_roles = object.get_local_roles()
1627 prefix = self.getGroupPrefix()
1628 for one_user in local_roles:
1629 massagedUsername = username = one_user[0]
1630 roles = one_user[1]
1631 userType = 'user'
1632 if prefix:
1633 if self.getGroupById(username) is not None:
1634 massagedUsername = username[len(prefix):]
1635 userType = 'group'
1636 else:
1637 userType = 'unknown'
1638 result.append((massagedUsername, roles, userType, username))
1639 return tuple(result)
1640
1641
1642 security.declarePublic("getAllLocalRoles")
1643 def getAllLocalRoles(self, object):
1644 """getAllLocalRoles(self, object): return a dictionnary {useratom_id: roles} of local
1645 roles defined AND herited at a certain point. This will handle lr-blocking
1646 as well.
1647 """
1648 # Perform security check on destination object
1649 if not getSecurityManager().checkPermission(Permissions.change_permissions, object):
1650 raise Unauthorized(name = "getAllLocalRoles")
1651
1652 return self._getAllLocalRoles(object)
1653
1654
1655 def _getAllLocalRoles(self, object):
1656 """getAllLocalRoles(self, object): return a dictionnary {useratom_id: roles} of local
1657 roles defined AND herited at a certain point. This will handle lr-blocking
1658 as well.
1659 """
1660 # Modified from AccessControl.User.getRolesInContext().
1661 merged = {}
1662 object = getattr(object, 'aq_inner', object)
1663 while 1:
1664 if hasattr(object, '__ac_local_roles__'):
1665 dict = object.__ac_local_roles__ or {}
1666 if callable(dict): dict = dict()
1667 for k, v in dict.items():
1668 if not merged.has_key(k):
1669 merged[k] = {}
1670 for role in v:
1671 merged[k][role] = 1
1672 if not self.isLocalRoleAcquired(object):
1673 break
1674 if hasattr(object, 'aq_parent'):
1675 object=object.aq_parent
1676 object=getattr(object, 'aq_inner', object)
1677 continue
1678 if hasattr(object, 'im_self'):
1679 object=object.im_self
1680 object=getattr(object, 'aq_inner', object)
1681 continue
1682 break
1683 for key, value in merged.items():
1684 merged[key] = value.keys()
1685 return merged
1686
1687
1688
1689 # Plone-specific security matrix computing method.
1690 security.declarePublic("getPloneSecurityMatrix")
1691 def getPloneSecurityMatrix(self, object):
1692 """getPloneSecurityMatrix(self, object): return a list of dicts of the current object
1693 and all its parents. The list is sorted with portal object first.
1694 Each dict has the following structure:
1695 {
1696 depth: (0 for portal root, 1 for 1st-level folders and so on),
1697 id:
1698 title:
1699 icon:
1700 absolute_url:
1701 security_permission: true if current user can change security on this object
1702 state: (workflow state)
1703 acquired_local_roles: 0 if local role blocking is enabled for this folder
1704 roles: {
1705 'role1': {
1706 'all_local_roles': [r1, r2, r3, ] (all defined local roles, including parent ones)
1707 'defined_local_roles': [r3, ] (local-defined only local roles)
1708 'permissions': ['Access contents information', 'Modify portal content', ] (only a subset)
1709 'same_permissions': true if same permissions as the parent
1710 'same_all_local_roles': true if all_local_roles is the same as the parent
1711 'same_defined_local_roles': true if defined_local_roles is the same as the parent
1712 },
1713 'role2': {...},
1714 },
1715 }
1716 """
1717 # Perform security check on destination object
1718 if not getSecurityManager().checkPermission(Permissions.access_contents_information, object):
1719 raise Unauthorized(name = "getPloneSecurityMatrix")
1720
1721 # Basic inits
1722 mt = self.portal_membership
1723
1724 # Fetch all possible roles in the portal
1725 all_roles = ['Anonymous'] + mt.getPortalRoles()
1726
1727 # Fetch parent folders list until the portal
1728 all_objects = []
1729 cur_object = object
1730 while 1:
1731 if not getSecurityManager().checkPermission(Permissions.access_contents_information, cur_object):
1732 raise Unauthorized(name = "getPloneSecurityMatrix")
1733 all_objects.append(cur_object)
1734 if cur_object.meta_type == "Plone Site":
1735 break
1736 cur_object = object.aq_parent
1737 all_objects.reverse()
1738
1739 # Scan those folders to get all the required information about them
1740 ret = []
1741 previous = None
1742 count = 0
1743 for obj in all_objects:
1744 # Basic information
1745 current = {
1746 "depth": count,
1747 "id": obj.getId(),
1748 "title": obj.Title(),
1749 "icon": obj.getIcon(),
1750 "absolute_url": obj.absolute_url(),
1751 "security_permission": getSecurityManager().checkPermission(Permissions.change_permissions, obj),
1752 "acquired_local_roles": self.isLocalRoleAcquired(obj),
1753 "roles": {},
1754 "state": "XXX TODO XXX", # XXX TODO
1755 }
1756 count += 1
1757
1758 # Workflow state
1759 # XXX TODO
1760
1761 # Roles
1762 all_local_roles = {}
1763 local_roles = self._getAllLocalRoles(obj)
1764 for user, roles in self._getAllLocalRoles(obj).items():
1765 for role in roles:
1766 if not all_local_roles.has_key(role):
1767 all_local_roles[role] = {}
1768 all_local_roles[role][user] = 1
1769 defined_local_roles = {}
1770 if hasattr(obj.aq_base, 'get_local_roles'):
1771 for user, roles in obj.get_local_roles():
1772 for role in roles:
1773 if not defined_local_roles.has_key(role):
1774 defined_local_roles[role] = {}
1775 defined_local_roles[role][user] = 1
1776
1777 for role in all_roles:
1778 all = all_local_roles.get(role, {}).keys()
1779 defined = defined_local_roles.get(role, {}).keys()
1780 all.sort()
1781 defined.sort()
1782 same_all_local_roles = 0
1783 same_defined_local_roles = 0
1784 if previous:
1785 if previous['roles'][role]['all_local_roles'] == all:
1786 same_all_local_roles = 1
1787 if previous['roles'][role]['defined_local_roles'] == defined:
1788 same_defined_local_roles = 1
1789
1790 current['roles'][role] = {
1791 "all_local_roles": all,
1792 "defined_local_roles": defined,
1793 "same_all_local_roles": same_all_local_roles,
1794 "same_defined_local_roles": same_defined_local_roles,
1795 "permissions": [], # XXX TODO
1796 }
1797
1798 ret.append(current)
1799 previous = current
1800
1801 return ret
1802
1803
1804 security.declareProtected(Permissions.manage_users, "computeSecuritySettings")
1805 def computeSecuritySettings(self, folders, actors, permissions, cache = {}):
1806 """
1807 computeSecuritySettings(self, folders, actors, permissions, cache = {}) => return a structure that is suitable for security audit Page Template.
1808
1809 - folders is the structure returned by getSiteTree()
1810 - actors is the structure returned by listUsersAndRoles()
1811 - permissions is ((id: permission), (id: permission), ...)
1812 - cache is passed along requests to make computing faster
1813 """
1814 # Scan folders and actors to get the relevant information
1815 usr_cache = {}
1816 for id, depth, path in folders:
1817 folder = self.unrestrictedTraverse(path)
1818 for kind, actor, display, handle, html in actors:
1819 if kind in ("user", "group"):
1820 # Init structure
1821 if not cache.has_key(path):
1822 cache[path] = {(kind, actor): {}}
1823 elif not cache[path].has_key((kind, actor)):
1824 cache[path][(kind, actor)] = {}
1825 else:
1826 cache[path][(kind, actor)] = {}
1827
1828 # Split kind into groups and get individual role information
1829 perm_keys = []
1830 usr = usr_cache.get(actor)
1831 if not usr:
1832 usr = self.getUser(actor)
1833 usr_cache[actor] = usr
1834 roles = usr.getRolesInContext(folder,)
1835 for role in roles:
1836 for perm_key in self.computeSetting(path, folder, role, permissions, cache).keys():
1837 cache[path][(kind, actor)][perm_key] = 1
1838
1839 else:
1840 # Get role information
1841 self.computeSetting(path, folder, actor, permissions, cache)
1842
1843 # Return the computed cache
1844 return cache
1845
1846
1847 security.declareProtected(Permissions.manage_users, "computeSetting")
1848 def computeSetting(self, path, folder, actor, permissions, cache):
1849 """
1850 computeSetting(......) => used by computeSecuritySettings to populate the cache for ROLES
1851 """
1852 # Avoid doing things twice
1853 kind = "role"
1854 if cache.get(path, {}).get((kind, actor), None) is not None:
1855 return cache[path][(kind, actor)]
1856
1857 # Initilize cache structure
1858 if not cache.has_key(path):
1859 cache[path] = {(kind, actor): {}}
1860 elif not cache[path].has_key((kind, actor)):
1861 cache[path][(kind, actor)] = {}
1862
1863 # Analyze permission settings
1864 ps = folder.permission_settings()
1865 for perm_key, permission in permissions:
1866 # Check acquisition of permission setting.
1867 can = 0
1868 acquired = 0
1869 for p in ps:
1870 if p['name'] == permission:
1871 acquired = not not p['acquire']
1872
1873 # If acquired, call the parent recursively
1874 if acquired:
1875 parent = folder.aq_parent.getPhysicalPath()
1876 perms = self.computeSetting(parent, self.unrestrictedTraverse(parent), actor, permissions, cache)
1877 can = perms.get(perm_key, None)
1878
1879 # Else, check permission here
1880 else:
1881 for p in folder.rolesOfPermission(permission):
1882 if p['name'] == "Anonymous":
1883 # If anonymous is allowed, then everyone is allowed
1884 if p['selected']:
1885 can = 1
1886 break
1887 if p['name'] == actor:
1888 if p['selected']:
1889 can = 1
1890 break
1891
1892 # Extend the data structure according to 'can' setting
1893 if can:
1894 cache[path][(kind, actor)][perm_key] = 1
1895
1896 return cache[path][(kind, actor)]
1897
1898
1899 security.declarePrivate('_getNextHandle')
1900 def _getNextHandle(self, index):
1901 """
1902 _getNextHandle(self, index) => utility function to
1903 get an unique handle for each legend item.
1904 """
1905 return "%02d" % index
1906
1907
1908 security.declareProtected(Permissions.manage_users, "listUsersAndRoles")
1909 def listUsersAndRoles(self,):
1910 """
1911 listUsersAndRoles(self,) => list of tuples
1912
1913 This method is used by the Security Audit page.
1914 XXX HAS TO BE OPTIMIZED
1915 """
1916 request = self.REQUEST
1917 display_roles = request.get('display_roles', 0)
1918 display_groups = request.get('display_groups', 0)
1919 display_users = request.get('display_users', 0)
1920
1921 role_index = 0
1922 user_index = 0
1923 group_index = 0
1924 ret = []
1925
1926 # Collect roles
1927 if display_roles:
1928 for r in self.aq_parent.valid_roles():
1929 handle = "R%02d" % role_index
1930 role_index += 1
1931 ret.append(('role', r, r, handle, r))
1932
1933 # Collect users
1934 if display_users:
1935 for u in map(lambda x: x.getId(), self.getPureUsers()):
1936 obj = self.getUser(u)
1937 html = obj.asHTML()
1938 handle = "U%02d" % user_index
1939 user_index += 1
1940 ret.append(('user', u, u, handle, html))
1941
1942 if display_groups:
1943 for u in self.getGroupNames():
1944 obj = self.getUser(u)
1945 handle = "G%02d" % group_index
1946 html = obj.asHTML()
1947 group_index += 1
1948 ret.append(('group', u, obj.getUserNameWithoutGroupPrefix(), handle, html))
1949
1950 # Return list
1951 return ret
1952
1953 security.declareProtected(Permissions.manage_users, "getSiteTree")
1954 def getSiteTree(self, obj=None, depth=0):
1955 """
1956 getSiteTree(self, obj=None, depth=0) => special structure
1957
1958 This is used by the security audit page
1959 """
1960 ret = []
1961 if not obj:
1962 if depth==0:
1963 obj = self.aq_parent
1964 else:
1965 return ret
1966
1967 ret.append([obj.getId(), depth, string.join(obj.getPhysicalPath(), '/')])
1968 for sub in obj.objectValues():
1969 try:
1970 # Ignore user folders
1971 if sub.getId() in ('acl_users', ):
1972 continue
1973
1974 # Ignore portal_* stuff
1975 if sub.getId()[:len('portal_')] == 'portal_':
1976 continue
1977
1978 if sub.isPrincipiaFolderish:
1979 ret.extend(self.getSiteTree(sub, depth + 1))
1980
1981 except:
1982 # We ignore exceptions
1983 pass
1984
1985 return ret
1986
1987 security.declareProtected(Permissions.manage_users, "listAuditPermissions")
1988 def listAuditPermissions(self,):
1989 """
1990 listAuditPermissions(self,) => return a list of eligible permissions
1991 """
1992 ps = self.permission_settings()
1993 return map(lambda p: p['name'], ps)
1994
1995 security.declareProtected(Permissions.manage_users, "getDefaultPermissions")
1996 def getDefaultPermissions(self,):
1997 """
1998 getDefaultPermissions(self,) => return default R & W permissions for security audit.
1999 """
2000 # If there's a Plone site in the above folder, use plonish permissions
2001 hasPlone = 0
2002 p = self.aq_parent
2003 if p.meta_type == "CMF Site":
2004 hasPlone = 1
2005 else:
2006 for obj in p.objectValues():
2007 if obj.meta_type == "CMF Site":
2008 hasPlone = 1
2009 break
2010
2011 if hasPlone:
2012 return {'R': 'View',
2013 'W': 'Modify portal content',
2014 }
2015 else:
2016 return {'R': 'View',
2017 'W': 'Change Images and Files',
2018 }
2019
2020
2021 # #
2022 # Users/Groups tree view #
2023 # (ZMI only) #
2024 # #
2025
2026
2027 security.declarePrivate('getTreeInfo')
2028 def getTreeInfo(self, usr, dict = {}):
2029 "utility method"
2030 # Prevend infinite recursions
2031 name = usr.getUserName()
2032 if dict.has_key(name):
2033 return
2034 dict[name] = {}
2035
2036 # Properties
2037 noprefix = usr.getUserNameWithoutGroupPrefix()
2038 is_group = usr.isGroup()
2039 if usr.isGroup():
2040 icon = string.join(self.getPhysicalPath(), '/') + '/img_group'
2041 ## icon = self.absolute_url() + '/img_group'
2042 else:
2043 icon = ' img_user'
2044 ## icon = self.absolute_url() + '/img_user'
2045
2046 # Subobjects
2047 belongs_to = []
2048 for grp in usr.getGroups(no_recurse = 1):
2049 belongs_to.append(grp)
2050 self.getTreeInfo(self.getGroup(grp))
2051
2052 # Append (and return) structure
2053 dict[name] = {
2054 "name": noprefix,
2055 "is_group": is_group,
2056 "icon": icon,
2057 "belongs_to": belongs_to,
2058 }
2059 return dict
2060
2061
2062 security.declarePrivate("tpValues")
2063 def tpValues(self):
2064 # Avoid returning HUUUUUUGE lists
2065 # Use the cache at first
2066 if self._v_no_tree and self._v_cache_no_tree > time.time():
2067 return [] # Do not use the tree
2068
2069 # XXX - I DISABLE THE TREE BY NOW (Pb. with icon URL)
2070 return []
2071
2072 # Then, use a simple computation to determine opportunity to use the tree or not
2073 ngroups = len(self.getGroupNames())
2074 if ngroups > MAX_TREE_USERS_AND_GROUPS:
2075 self._v_no_tree = 1
2076 self._v_cache_no_tree = time.time() + TREE_CACHE_TIME
2077 return []
2078 nusers = len(self.getUsers())
2079 if ngroups + nusers > MAX_TREE_USERS_AND_GROUPS:
2080 meth_list = self.getGroups
2081 else:
2082 meth_list = self.getUsers
2083 self._v_no_tree = 0
2084
2085 # Get top-level user and groups list
2086 tree_dict = {}
2087 top_level_names = []
2088 top_level = []
2089 for usr in meth_list():
2090 self.getTreeInfo(usr, tree_dict)
2091 if not usr.getGroups(no_recurse = 1):
2092 top_level_names.append(usr.getUserName())
2093 for id in top_level_names:
2094 top_level.append(treeWrapper(id, tree_dict))
2095
2096 # Return this top-level list
2097 top_level.sort(lambda x, y: cmp(x.sortId(), y.sortId()))
2098 return top_level
2099
2100
2101 def tpId(self,):
2102 return self.getId()
2103
2104
2105 # #
2106 # Direct traversal to user or group info #
2107 # #
2108
2109 def manage_workspace(self, REQUEST):
2110 """
2111 manage_workspace(self, REQUEST) => Overrided to allow direct user or group traversal
2112 via the left tree view.
2113 """
2114 path = string.split(REQUEST.PATH_INFO, '/')[:-1]
2115 userid = path[-1]
2116
2117 # Use individual usr/grp management screen (only if name is passed along the mgt URL)
2118 if userid != "acl_users":
2119 usr = self.getUserById(userid)
2120 if usr:
2121 REQUEST.set('username', userid)
2122 REQUEST.set('MANAGE_TABS_NO_BANNER', '1') # Prevent use of the manage banner
2123 return self.restrictedTraverse('manage_user')()
2124
2125 # Default management screen
2126 return self.restrictedTraverse('manage_overview')()
2127
2128
2129 # Tree caching information
2130 _v_no_tree = 0
2131 _v_cache_no_tree = 0
2132 _v_cache_tree = (0, [])
2133
2134
2135 def __bobo_traverse__(self, request, name):
2136 """
2137 Looks for the name of a user or a group.
2138 This applies only if users list is not huge.
2139 """
2140 # Check if it's an attribute
2141 if hasattr(self.aq_base, name, ):
2142 return getattr(self, name)
2143
2144 # It's not an attribute, maybe it's a user/group
2145 # (this feature is used for the tree)
2146 if name.startswith('_'):
2147 pass # Do not fetch users
2148 elif name.startswith('manage_'):
2149 pass # Do not fetch users
2150 elif name in INVALID_USER_NAMES:
2151 pass # Do not fetch users
2152 else:
2153 # Only try to get users is fetch_user is true.
2154 # This is only for performance reasons.
2155 # The following code block represent what we want to minimize
2156 if self._v_cache_tree[0] < time.time():
2157 un = map(lambda x: x.getId(), self.getUsers()) # This is the cost we want to avoid
2158 self._v_cache_tree = (time.time() + TREE_CACHE_TIME, un, )
2159 else:
2160 un = self._v_cache_tree[1]
2161
2162 # Get the user if we can
2163 if name in un:
2164 self._v_no_tree = 0
2165 return self
2166
2167 # Force getting the user if we must
2168 if request.get("FORCE_USER"):
2169 self._v_no_tree = 0
2170 return self
2171
2172 # This will raise if it's not possible to acquire 'name'
2173 return getattr(self, name, )
2174
2175
2176
2177 # #
2178 # USERS / GROUPS BATCHING (ZMI SCREENS) #
2179 # #
2180
2181 _v_batch_users = []
2182
2183 security.declareProtected(Permissions.view_management_screens, "listUsersBatches")
2184 def listUsersBatches(self,):
2185 """
2186 listUsersBatches(self,) => return a list of (start, end) tuples.
2187 Return None if batching is not necessary
2188 """
2189 # Time-consuming stuff !
2190 un = map(lambda x: x.getId(), self.getPureUsers())
2191 if len(un) <= MAX_USERS_PER_PAGE:
2192 return None
2193 un.sort()
2194
2195 # Split this list into small groups if necessary
2196 ret = []
2197 idx = 0
2198 l_un = len(un)
2199 nbatches = int(math.ceil(l_un / float(MAX_USERS_PER_PAGE)))
2200 for idx in range(0, nbatches):
2201 first = idx * MAX_USERS_PER_PAGE
2202 last = first + MAX_USERS_PER_PAGE - 1
2203 if last >= l_un:
2204 last = l_un - 1
2205 # Append a tuple (not dict) to avoid too much memory consumption
2206 ret.append((first, last, un[first], un[last]))
2207
2208 # Cache & return it
2209 self._v_batch_users = un
2210 return ret
2211
2212 security.declareProtected(Permissions.view_management_screens, "listUsersBatchTable")
2213 def listUsersBatchTable(self,):
2214 """
2215 listUsersBatchTable(self,) => Same a mgt screens but divided into sublists to
2216 present them into 5 columns.
2217 XXX have to merge this w/getUsersBatch to make it in one single pass
2218 """
2219 # Iterate
2220 ret = []
2221 idx = 0
2222 current = []
2223 for rec in (self.listUsersBatches() or []):
2224 if not idx % 5:
2225 if current:
2226 ret.append(current)
2227 current = []
2228 current.append(rec)
2229 idx += 1
2230
2231 if current:
2232 ret.append(current)
2233
2234 return ret
2235
2236 security.declareProtected(Permissions.view_management_screens, "getUsersBatch")
2237 def getUsersBatch(self, start):
2238 """
2239 getUsersBatch(self, start) => user list
2240 """
2241 # Rebuild the list if necessary
2242 if not self._v_batch_users:
2243 un = map(lambda x: x.getId(), self.getPureUsers())
2244 self._v_batch_users = un
2245
2246 # Return the batch
2247 end = start + MAX_USERS_PER_PAGE
2248 ids = self._v_batch_users[start:end]
2249 ret = []
2250 for id in ids:
2251 usr = self.getUser(id)
2252 if usr: # Prevent adding invalid users
2253 ret.append(usr)
2254 return ret
2255
2256
2257 # #
2258 # Multiple sources management #
2259 # #
2260
2261 # Arrows
2262 img_up_arrow = ImageFile.ImageFile('www/up_arrow.gif', globals())
2263 img_down_arrow = ImageFile.ImageFile('www/down_arrow.gif', globals())
2264 img_up_arrow_grey = ImageFile.ImageFile('www/up_arrow_grey.gif', globals())
2265 img_down_arrow_grey = ImageFile.ImageFile('www/down_arrow_grey.gif', globals())
2266
2267 security.declareProtected(Permissions.manage_users, "toggleSource")
2268 def toggleSource(self, src_id, REQUEST = {}):
2269 """
2270 toggleSource(self, src_id, REQUEST = {}) => toggle enabled/disabled source
2271 """
2272 # Find the source
2273 ids = self.objectIds('GRUFUsers')
2274 if not src_id in ids:
2275 raise ValueError, "Invalid source: '%s' (%s)" % (src_id, ids)
2276 src = getattr(self, src_id)
2277 if src.enabled:
2278 src.disableSource()
2279 else:
2280 src.enableSource()
2281
2282 # Redirect where we want to
2283 if REQUEST.has_key('RESPONSE'):
2284 return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
2285
2286
2287 security.declareProtected(Permissions.manage_users, "listUserSources")
2288 def listUserSources(self, ):
2289 """
2290 listUserSources(self, ) => Return a list of userfolder objects
2291 Only return VALID (ie containing an acl_users) user sources if all is None
2292 XXX HAS TO BE OPTIMIZED VERY MUCH!
2293 We add a check in debug mode to ensure that invalid sources won't be added
2294 to the list.
2295 This method return only _enabled_ user sources.
2296 """
2297 ret = []
2298 dret = {}
2299 if DEBUG_MODE:
2300 for src in self.objectValues(['GRUFUsers']):
2301 if not src.enabled:
2302 continue
2303 if 'acl_users' in src.objectIds():
2304 if getattr(aq_base(src.acl_users), 'authenticate', None): # Additional check in debug mode
2305 dret[src.id] = src.acl_users # we cannot use restrictedTraverse here because
2306 # of infinite recursion issues.
2307 else:
2308 for src in self.objectValues(['GRUFUsers']):
2309 if not src.enabled:
2310 continue
2311 if not 'acl_users' in src.objectIds():
2312 continue
2313 dret[src.id] = src.acl_users
2314 ret = dret.items()
2315 ret.sort()
2316 return [ src[1] for src in ret ]
2317
2318 security.declareProtected(Permissions.manage_users, "listUserSourceFolders")
2319 def listUserSourceFolders(self, ):
2320 """
2321 listUserSources(self, ) => Return a list of GRUFUsers objects
2322 """
2323 ret = []
2324 for src in self.objectValues(['GRUFUsers']):
2325 ret.append(src)
2326 ret.sort(lambda x,y: cmp(x.id, y.id))
2327 return ret
2328
2329 security.declarePrivate("getUserSource")
2330 def getUserSource(self, id):
2331 """
2332 getUserSource(self, id) => GRUFUsers.acl_users object.
2333 Raises if no acl_users available
2334 """
2335 return getattr(self, id).acl_users
2336
2337 security.declarePrivate("getUserSourceFolder")
2338 def getUserSourceFolder(self, id):
2339 """
2340 getUserSourceFolder(self, id) => GRUFUsers object
2341 """
2342 return getattr(self, id)
2343
2344 security.declareProtected(Permissions.manage_users, "addUserSource")
2345 def addUserSource(self, factory_uri, REQUEST = {}, *args, **kw):
2346 """
2347 addUserSource(self, factory_uri, REQUEST = {}, *args, **kw) => redirect
2348 Adds the specified user folder
2349 """
2350 # Get the initial Users id
2351 ids = self.objectIds('GRUFUsers')
2352 if ids:
2353 ids.sort()
2354 if ids == ['Users',]:
2355 last = 0
2356 else:
2357 last = int(ids[-1][-2:])
2358 next_id = "Users%02d" % (last + 1, )
2359 else:
2360 next_id = "Users"
2361
2362 # Add the GRUFFolder object
2363 uf = GRUFFolder.GRUFUsers(id = next_id)
2364 self._setObject(next_id, uf)
2365
2366 ## # If we use ldap, tag it
2367 ## if string.find(factory_uri.lower(), "ldap") > -1:
2368 ## self._haveLDAPUF += 1
2369
2370 # Add its underlying UserFolder
2371 # If we're called TTW, uses a redirect else tries to call the UF factory directly
2372 if REQUEST.has_key('RESPONSE'):
2373 return REQUEST.RESPONSE.redirect("%s/%s/%s" % (self.absolute_url(), next_id, factory_uri))
2374 return getattr(self, next_id).unrestrictedTraverse(factory_uri)(*args, **kw)
2375 addUserSource = postonly(addUserSource)
2376
2377 security.declareProtected(Permissions.manage_users, "deleteUserSource")
2378 def deleteUserSource(self, id = None, REQUEST = {}):
2379 """
2380 deleteUserSource(self, id = None, REQUEST = {}) => Delete the specified user source
2381 """
2382 # Check the source id
2383 if type(id) != type('s'):
2384 raise ValueError, "You must choose a valid source to delete and confirm it."
2385
2386 # Delete it
2387 self.manage_delObjects([id,])
2388 if REQUEST.has_key('RESPONSE'):
2389 return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
2390 deleteUserSource = postonly(deleteUserSource)
2391
2392 security.declareProtected(Permissions.manage_users, "getDefaultUserSource")
2393 def getDefaultUserSource(self,):
2394 """
2395 getDefaultUserSource(self,) => acl_users object
2396 Return default user source for user writing.
2397 XXX By now, the FIRST source is the default one. This may change in the future.
2398 """
2399 lst = self.listUserSources()
2400 if not lst:
2401 raise RuntimeError, "No valid User Source to add users in."
2402 return lst[0]
2403
2404
2405 security.declareProtected(Permissions.manage_users, "listAvailableUserSources")
2406 def listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1):
2407 """
2408 listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1) => tuples (name, factory_uri)
2409 List UserFolder replacement candidates.
2410
2411 - if filter_classes is true, return only ones which have a base UserFolder class
2412 - if filter_permissions, return only types the user has rights to add
2413 """
2414 ret = []
2415
2416 # Fetch candidate types
2417 user = getSecurityManager().getUser()
2418 meta_types = []
2419 if callable(self.all_meta_types):
2420 all=self.all_meta_types()
2421 else:
2422 all=self.all_meta_types
2423 for meta_type in all:
2424 if filter_permissions and meta_type.has_key('permission'):
2425 if user.has_permission(meta_type['permission'],self):
2426 meta_types.append(meta_type)
2427 else:
2428 meta_types.append(meta_type)
2429
2430 # Keep only, if needed, BasicUserFolder-derived classes
2431 for t in meta_types:
2432 if t['name'] == self.meta_type:
2433 continue # Do not keep GRUF ! ;-)
2434
2435 if filter_classes:
2436 try:
2437 if t.get('instance', None) and t['instance'].isAUserFolder:
2438 ret.append((t['name'], t['action']))
2439 continue
2440 if t.get('instance', None) and class_utility.isBaseClass(AccessControl.User.BasicUserFolder, t['instance']):
2441 ret.append((t['name'], t['action']))
2442 continue
2443 except AttributeError:
2444 pass # We ignore 'invalid' instances (ie. that wouldn't define a __base__ attribute)
2445 else:
2446 ret.append((t['name'], t['action']))
2447
2448 return tuple(ret)
2449
2450 security.declareProtected(Permissions.manage_users, "moveUserSourceUp")
2451 def moveUserSourceUp(self, id, REQUEST = {}):
2452 """
2453 moveUserSourceUp(self, id, REQUEST = {}) => used in management screens
2454 try to get ids as consistant as possible
2455 """
2456 # List and sort sources and preliminary checks
2457 ids = self.objectIds('GRUFUsers')
2458 ids.sort()
2459 if not ids or not id in ids:
2460 raise ValueError, "Invalid User Source: '%s'" % (id,)
2461
2462 # Find indexes to swap
2463 src_index = ids.index(id)
2464 if src_index == 0:
2465 raise ValueError, "Cannot move '%s' User Source up." % (id, )
2466 dest_index = src_index - 1
2467
2468 # Find numbers to swap, fix them if they have more than 1 as offset
2469 if ids[dest_index] == 'Users':
2470 dest_num = 0
2471 else:
2472 dest_num = int(ids[dest_index][-2:])
2473 src_num = dest_num + 1
2474
2475 # Get ids
2476 src_id = id
2477 if dest_num == 0:
2478 dest_id = "Users"
2479 else:
2480 dest_id = "Users%02d" % (dest_num,)
2481 tmp_id = "%s_" % (dest_id, )
2482
2483 # Perform the swap
2484 self._renameUserSource(src_id, tmp_id)
2485 self._renameUserSource(dest_id, src_id)
2486 self._renameUserSource(tmp_id, dest_id)
2487
2488 # Return back to the forms
2489 if REQUEST.has_key('RESPONSE'):
2490 return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
2491 moveUserSourceUp = postonly(moveUserSourceUp)
2492
2493 security.declareProtected(Permissions.manage_users, "moveUserSourceDown")
2494 def moveUserSourceDown(self, id, REQUEST = {}):
2495 """
2496 moveUserSourceDown(self, id, REQUEST = {}) => used in management screens
2497 try to get ids as consistant as possible
2498 """
2499 # List and sort sources and preliminary checks
2500 ids = self.objectIds('GRUFUsers')
2501 ids.sort()
2502 if not ids or not id in ids:
2503 raise ValueError, "Invalid User Source: '%s'" % (id,)
2504
2505 # Find indexes to swap
2506 src_index = ids.index(id)
2507 if src_index == len(ids) - 1:
2508 raise ValueError, "Cannot move '%s' User Source up." % (id, )
2509 dest_index = src_index + 1
2510
2511 # Find numbers to swap, fix them if they have more than 1 as offset
2512 if id == 'Users':
2513 dest_num = 1
2514 else:
2515 dest_num = int(ids[dest_index][-2:])
2516 src_num = dest_num - 1
2517
2518 # Get ids
2519 src_id = id
2520 if dest_num == 0:
2521 dest_id = "Users"
2522 else:
2523 dest_id = "Users%02d" % (dest_num,)
2524 tmp_id = "%s_" % (dest_id, )
2525
2526 # Perform the swap
2527 self._renameUserSource(src_id, tmp_id)
2528 self._renameUserSource(dest_id, src_id)
2529 self._renameUserSource(tmp_id, dest_id)
2530
2531 # Return back to the forms
2532 if REQUEST.has_key('RESPONSE'):
2533 return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
2534 moveUserSourceDown = postonly(moveUserSourceDown)
2535
2536
2537 security.declarePrivate('_renameUserSource')
2538 def _renameUserSource(self, id, new_id, ):
2539 """
2540 Rename a particular sub-object.
2541 Taken fro CopySupport.manage_renameObject() code, modified to disable verifications.
2542 """
2543 try: self._checkId(new_id)
2544 except: raise CopyError, MessageDialog(
2545 title='Invalid Id',
2546 message=sys.exc_info()[1],
2547 action ='manage_main')
2548 ob=self._getOb(id)
2549 ## if not ob.cb_isMoveable():
2550 ## raise "Copy Error", eNotSupported % id
2551 ## self._verifyObjectPaste(ob) # This is what we disable
2552 try: ob._notifyOfCopyTo(self, op=1)
2553 except: raise CopyError, MessageDialog(
2554 title='Rename Error',
2555 message=sys.exc_info()[1],
2556 action ='manage_main')
2557 self._delObject(id)
2558 ob = aq_base(ob)
2559 ob._setId(new_id)
2560
2561 # Note - because a rename always keeps the same context, we
2562 # can just leave the ownership info unchanged.
2563 self._setObject(new_id, ob, set_owner=0)
2564
2565
2566 security.declareProtected(Permissions.manage_users, "replaceUserSource")
2567 def replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw):
2568 """
2569 replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw) => perform user source replacement
2570
2571 If new_factory is None, find it inside REQUEST (useful for ZMI screens)
2572 """
2573 # Check the source id
2574 if type(id) != type('s'):
2575 raise ValueError, "You must choose a valid source to replace and confirm it."
2576
2577 # Retreive factory if not explicitly passed
2578 if not new_factory:
2579 for record in REQUEST.get("source_rec", []):
2580 if record['id'] == id:
2581 new_factory = record['new_factory']
2582 break
2583 if not new_factory:
2584 raise ValueError, "You must select a new User Folder type."
2585
2586 # Delete the former one
2587 us = getattr(self, id)
2588 if "acl_users" in us.objectIds():
2589 us.manage_delObjects(['acl_users'])
2590
2591 ## If we use ldap, tag it
2592 #if string.find(new_factory.lower(), "ldap") > -1:
2593 # self._haveLDAPUF += 1
2594
2595 # Re-create the underlying UserFolder
2596 # If we're called TTW, uses a redirect else tries to call the UF factory directly
2597 if REQUEST.has_key('RESPONSE'):
2598 return REQUEST.RESPONSE.redirect("%s/%s/%s" % (self.absolute_url(), id, new_factory))
2599 return us.unrestrictedTraverse(new_factory)(*args, **kw) # XXX minor security pb ?
2600 replaceUserSource = postonly(replaceUserSource)
2601
2602
2603 security.declareProtected(Permissions.manage_users, "hasLDAPUserFolderSource")
2604 def hasLDAPUserFolderSource(self, ):
2605 """
2606 hasLDAPUserFolderSource(self,) => boolean
2607 Return true if a LUF source is instanciated.
2608 """
2609 for src in self.listUserSources():
2610 if src.meta_type == "LDAPUserFolder":
2611 return 1
2612 return None
2613
2614
2615 security.declareProtected(Permissions.manage_users, "updateLDAPUserFolderMapping")
2616 def updateLDAPUserFolderMapping(self, REQUEST = None):
2617 """
2618 updateLDAPUserFolderMapping(self, REQUEST = None) => None
2619
2620 Update the first LUF source in the process so that LDAP-group-to-Zope-role mapping
2621 is done.
2622 This is done by calling the appropriate method in LUF and affecting all 'group_' roles
2623 to the matching LDAP groups.
2624 """
2625 # Fetch all groups
2626 groups = self.getGroupIds()
2627
2628 # Scan sources
2629 for src in self.listUserSources():
2630 if not src.meta_type == "LDAPUserFolder":
2631 continue
2632
2633 # Delete all former group mappings
2634 deletes = []
2635 for (grp, role) in src.getGroupMappings():
2636 if role.startswith('group_'):
2637 deletes.append(grp)
2638 src.manage_deleteGroupMappings(deletes)
2639
2640 # Append all group mappings if it can be done
2641 ldap_groups = src.getGroups(attr = "cn")
2642 for grp in groups:
2643 if src._local_groups:
2644 grp_name = grp
2645 else:
2646 grp_name = grp[len('group_'):]
2647 Log(LOG_DEBUG, "cheching", grp_name, "in", ldap_groups, )
2648 if not grp_name in ldap_groups:
2649 continue
2650 Log(LOG_DEBUG, "Map", grp, "to", grp_name)
2651 src.manage_addGroupMapping(
2652 grp_name,
2653 grp,
2654 )
2655
2656 # Return
2657 if REQUEST:
2658 return REQUEST.RESPONSE.redirect(
2659 self.absolute_url() + "/manage_wizard",
2660 )
2661 updateLDAPUserFolderMapping = postonly(updateLDAPUserFolderMapping)
2662
2663
2664 # #
2665 # The Wizard Section #
2666 # #
2667
2668 def listLDAPUserFolderMapping(self,):
2669 """
2670 listLDAPUserFolderMapping(self,) => utility method
2671 """
2672 ret = []
2673 gruf_done = []
2674 ldap_done = []
2675
2676 # Scan sources
2677 for src in self.listUserSources():
2678 if not src.meta_type == "LDAPUserFolder":
2679 continue
2680
2681 # Get all GRUF & LDAP groups
2682 if src._local_groups:
2683 gruf_ids = self.getGroupIds()
2684 else:
2685 gruf_ids = self.getGroupIds()
2686 ldap_mapping = src.getGroupMappings()
2687 ldap_groups = src.getGroups(attr = "cn")
2688 for grp,role in ldap_mapping:
2689 if role in gruf_ids:
2690 ret.append((role, grp))
2691 gruf_done.append(role)
2692 ldap_done.append(grp)
2693 if not src._local_groups:
2694 ldap_done.append(role)
2695 for grp in ldap_groups:
2696 if not grp in ldap_done:
2697 ret.append((None, grp))
2698 for grp in gruf_ids:
2699 if not grp in gruf_done:
2700 ret.append((grp, None))
2701 Log(LOG_DEBUG, "return", ret)
2702 return ret
2703
2704
2705 security.declareProtected(Permissions.manage_users, "getInvalidMappings")
2706 def getInvalidMappings(self,):
2707 """
2708 return true if LUF mapping looks good
2709 """
2710 wrong = []
2711 grufs = []
2712 for gruf, ldap in self.listLDAPUserFolderMapping():
2713 if gruf and ldap:
2714 continue
2715 if not gruf:
2716 continue
2717 if gruf.startswith('group_'):
2718 gruf = gruf[len('group_'):]
2719 grufs.append(gruf)
2720 for gruf, ldap in self.listLDAPUserFolderMapping():
2721 if gruf and ldap:
2722 continue
2723 if not ldap:
2724 continue
2725 if ldap.startswith('group_'):
2726 ldap = ldap[len('group_'):]
2727 if ldap in grufs:
2728 wrong.append(ldap)
2729
2730 return wrong
2731
2732 security.declareProtected(Permissions.manage_users, "getLUFSource")
2733 def getLUFSource(self,):
2734 """
2735 getLUFSource(self,) => Helper to get a pointer to the LUF src.
2736 Return None if not available
2737 """
2738 for src in self.listUserSources():
2739 if src.meta_type == "LDAPUserFolder":
2740 return src
2741
2742 security.declareProtected(Permissions.manage_users, "areLUFGroupsLocal")
2743 def areLUFGroupsLocal(self,):
2744 """return true if luf groups are stored locally"""
2745 return hasattr(self.getLUFSource(), '_local_groups')
2746
2747
2748 security.declareProtected(Permissions.manage_users, "haveLDAPGroupFolder")
2749 def haveLDAPGroupFolder(self,):
2750 """return true if LDAPGroupFolder is the groups source
2751 """
2752 return not not self.Groups.acl_users.meta_type == 'LDAPGroupFolder'
2753
2754 security.declarePrivate('searchGroups')
2755 def searchGroups(self, **kw):
2756 names = self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 1)
2757 return [{'id' : gn} for gn in names]
2758
2759
2760
2761 class treeWrapper:
2762 """
2763 treeWrapper: Wrapper around user/group objects for the tree
2764 """
2765 def __init__(self, id, tree, parents = []):
2766 """
2767 __init__(self, id, tree, parents = []) => wraps the user object for dtml-tree
2768 """
2769 # Prepare self-contained information
2770 self._id = id
2771 self.name = tree[id]['name']
2772 self.icon = tree[id]['icon']
2773 self.is_group = tree[id]['is_group']
2774 parents.append(id)
2775 self.path = parents
2776
2777 # Prepare subobjects information
2778 subobjects = []
2779 for grp_id in tree.keys():
2780 if id in tree[grp_id]['belongs_to']:
2781 subobjects.append(treeWrapper(grp_id, tree, parents))
2782 subobjects.sort(lambda x, y: cmp(x.sortId(), y.sortId()))
2783 self.subobjects = subobjects
2784
2785 def id(self,):
2786 return self.name
2787
2788 def sortId(self,):
2789 if self.is_group:
2790 return "__%s" % (self._id,)
2791 else:
2792 return self._id
2793
2794 def tpValues(self,):
2795 """
2796 Return 'subobjects'
2797 """
2798 return self.subobjects
2799
2800 def tpId(self,):
2801 return self._id
2802
2803 def tpURL(self,):
2804 return self.tpId()
2805
2806 InitializeClass(GroupUserFolder)