1 # -*- coding: utf-8 -*-
3 ## Copyright (C)2006 Ingeniweb
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 2 of the License, or
8 ## (at your option) any later version.
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program; see the file COPYING. If not, write to the
17 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 GroupUserFolder product
21 __version__
= "$Revision: $"
23 # $Id: GroupUserFolder.py 40118 2007-04-01 15:13:44Z alecm $
24 __docformat__
= 'restructuredtext'
27 # fakes a method from a DTML file
28 from Globals
import MessageDialog
, DTMLFile
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
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
54 from global_symbols
import *
55 import AccessControl
.User
58 from Products
.PageTemplates
import PageTemplateFile
60 from Products
.GroupUserFolder
import postonly
62 from interfaces
.IUserFolder
import IUserFolder
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.
72 def unique(sequence
, _list
= 0):
73 """Make a sequence a list of unique items"""
78 return list(uniquedict
.keys())
79 return tuple(uniquedict
.keys())
82 def manage_addGroupUserFolder(self
, dtself
=None, REQUEST
=None, **ignored
):
83 """ Factory method that creates a UserFolder"""
86 try: self
._setObject
('acl_users', f
)
87 except: return MessageDialog(
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
()
94 self
.acl_users
.Users
.manage_addUserFolder()
95 self
.acl_users
.Groups
.manage_addUserFolder()
97 if REQUEST
is not None:
98 REQUEST
['RESPONSE'].redirect(self
.absolute_url()+'/manage_main')
103 class GroupUserFolder(OFS
.ObjectManager
.ObjectManager
,
104 AccessControl
.User
.BasicUserFolder
,
107 GroupUserFolder => User folder with groups management
114 meta_type
='Group User Folder'
116 title
='Group-aware User Folder'
118 __implements__
= (IUserFolder
, )
119 def __creatable_by_emergency_user__(self
): return 1
121 isAnObjectManager
= 1
122 isPrincipiaFolderish
= 1
127 security
= ClassSecurityInfo()
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'},
138 OFS
.ObjectManager
.ObjectManager
.manage_options
+ \
139 RoleManager
.manage_options
+ \
140 Item
.manage_options
)
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())
156 'user_names', 'setDomainAuthenticationMode',
162 # Color constants, only useful within GRUF management screens
163 user_color
= "#006600"
164 group_color
= "#000099"
165 role_color
= "#660000"
167 # User and group images
168 img_user
= ImageFile
.ImageFile('www/GRUFUsers.gif', globals())
169 img_group
= ImageFile
.ImageFile('www/GRUFGroups.gif', globals())
174 # OFFICIAL INTERFACE #
177 security
.declarePublic("hasUsers")
178 def hasUsers(self
, ):
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."""
189 security
.declareProtected(Permissions
.manage_users
, "user_names")
190 def user_names(self
,):
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.
198 return self
.getUserIds()
201 security
.declareProtected(Permissions
.manage_users
, "getUserNames")
202 def getUserNames(self
, __include_groups__
= 1, __include_users__
= 1, __groups_prefixed__
= 0):
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 !]
209 if __include_users__
:
210 LogCallStack(LOG_DEBUG
, "This call can be VERY expensive!")
214 # Fetch users in user sources
215 if __include_users__
:
216 for src
in self
.listUserSources():
217 names
.extend(src
.getUserNames())
219 # Append groups if possible
220 if __include_groups__
:
222 if "acl_users" in self
._getOb
('Groups').objectIds():
223 names
.extend(self
.Groups
.listGroups(prefixed
= __groups_prefixed__
))
226 for ldapuf
in ldap_sources
:
227 if ldapuf
._local
_groups
:
229 for g
in ldapuf
.getGroups(attr
= LDAP_GROUP_RDN
):
230 if __groups_prefixed__
:
231 names
.append("%s%s" % (GROUP_PREFIX
, g
))
234 # Return a list of unique names
235 return unique(names
, _list
= 1)
237 security
.declareProtected(Permissions
.manage_users
, "getUserIds")
238 def getUserIds(self
,):
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 !]
246 return self
.getUserNames(__groups_prefixed__
= 1)
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.
257 Log(LOG_DEBUG
, "getUsers")
261 # avoid too many lookups for 'has_key' in loops
262 isUserProcessed
= names_set
.has_key
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():
270 continue # Ignore empty users
273 if isUserProcessed(name
):
274 continue # Prevent double users inclusion
277 names_set
[name
] = True
279 GRUFUser
.GRUFGroup(u
, self
, isGroup
= 1, source_id
= "Groups").__of
__(self
)
283 if __include_users__
:
284 for src
in self
.listUserSources():
285 for u
in src
.getUsers():
287 continue # Ignore empty users
290 if isUserProcessed(name
):
291 continue # Prevent double users inclusion
294 names_set
[name
] = True
296 GRUFUser
.GRUFUser(u
, self
, source_id
= src
.getUserSourceId(), isGroup
= 0).__of
__(self
)
301 security
.declareProtected(Permissions
.manage_users
, "getUser")
302 def getUser(self
, name
, __include_users__
= 1, __include_groups__
= 1, __force_group_id__
= 0):
304 Return the named user object or None.
305 User have precedence over group.
306 If name is None, getUser() will return None.
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():
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
:]
321 # Fetch regular groups
322 u
= self
._getOb
('Groups')._getGroup
(id)
324 ret
= GRUFUser
.GRUFGroup(
325 u
, self
, isGroup
= 1, source_id
= "Groups"
327 return ret
# XXX This violates precedence
330 if __include_users__
:
331 for src
in self
.listUserSources():
332 u
= src
.getUser(name
)
334 ret
= GRUFUser
.GRUFUser(u
, self
, source_id
= src
.getUserSourceId(), isGroup
= 0).__of
__(self
)
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
)
341 ret
= GRUFUser
.GRUFGroup(u
, self
, isGroup
= 1, source_id
= "Groups").__of
__(self
)
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.
351 ret
= self
.getUser(id, __force_group_id__
= 1)
353 if default
is _marker
:
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.]
366 # Try to fetch a user first
367 usr
= self
.getUser(name
)
369 # If not found, try to fetch a group by appending the prefix
371 name
= "%s%s" % (GROUP_PREFIX
, name
)
372 usr
= self
.getUserById(name
, default
)
376 security
.declareProtected(Permissions
.manage_users
, "getPureUserNames")
377 def getPureUserNames(self
, ):
378 """Fetch the list of actual users from GRUFUsers.
380 return self
.getUserNames(__include_groups__
= 0)
383 security
.declareProtected(Permissions
.manage_users
, "getPureUserIds")
384 def getPureUserIds(self
,):
385 """Same as getUserIds() but without groups
387 return self
.getUserNames(__include_groups__
= 0)
389 security
.declareProtected(Permissions
.manage_users
, "getPureUsers")
390 def getPureUsers(self
):
391 """Return a list of pure user objects.
393 return self
.getUsers(__include_groups__
= 0)
395 security
.declareProtected(Permissions
.manage_users
, "getPureUser")
396 def getPureUser(self
, id, ):
397 """Return the named user object or None"""
403 return self
.getUser(id, __include_groups__
= 0)
406 security
.declareProtected(Permissions
.manage_users
, "getGroupNames")
407 def getGroupNames(self
, ):
408 """Same as getUserNames() but without pure users.
410 return self
.getUserNames(__include_users__
= 0, __groups_prefixed__
= 0)
412 security
.declareProtected(Permissions
.manage_users
, "getGroupIds")
413 def getGroupIds(self
, ):
414 """Same as getUserNames() but without pure users.
416 return self
.getUserNames(__include_users__
= 0, __groups_prefixed__
= 1)
418 security
.declareProtected(Permissions
.manage_users
, "getGroups")
420 """Same as getUsers() but without pure users.
422 return self
.getUsers(__include_users__
= 0)
424 security
.declareProtected(Permissions
.manage_users
, "getGroup")
425 def getGroup(self
, name
, prefixed
= 1):
426 """Return the named user object or None"""
431 # Unprefix group name
432 if not name
.startswith(GROUP_PREFIX
):
433 name
= "%s%s" % (GROUP_PREFIX
, name
, )
436 return self
.getUser(name
, __include_users__
= 0)
438 security
.declareProtected(Permissions
.manage_users
, "getGroupById")
439 def getGroupById(self
, id, default
= _marker
):
440 """Same as getUserById(id) but forces returning a group.
442 ret
= self
.getUser(id, __include_users__
= 0, __force_group_id__
= 1)
444 if default
is _marker
:
449 security
.declareProtected(Permissions
.manage_users
, "getGroupByName")
450 def getGroupByName(self
, name
, default
= _marker
):
451 """Same as getUserByName(name) but forces returning a group.
453 ret
= self
.getUser(name
, __include_users__
= 0, __force_group_id__
= 0)
455 if default
is _marker
:
466 security
.declareProtected(Permissions
.manage_users
, "userFolderAddUser")
467 def userFolderAddUser(self
, name
, password
, roles
, domains
, groups
= (),
469 """API method for creating a new user object. Note that not all
470 user folder implementations support dynamic creation of user
473 return self
._doAddUser
(name
, password
, roles
, domains
, groups
, **kw
)
474 userFolderAddUser
= postonly(userFolderAddUser
)
476 security
.declareProtected(Permissions
.manage_users
, "userFolderEditUser")
477 def userFolderEditUser(self
, name
, password
, roles
, domains
, groups
= None,
479 """API method for changing user object attributes. Note that not
480 all user folder implementations support changing of user object
482 Arguments ARE required.
484 return self
._doChangeUser
(name
, password
, roles
, domains
, groups
, **kw
)
485 userFolderEditUser
= postonly(userFolderEditUser
)
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
493 Arguments are optional"""
494 return self
._updateUser
(name
, password
, roles
, domains
, groups
, **kw
)
495 userFolderUpdateUser
= postonly(userFolderUpdateUser
)
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
)
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.
508 while name
.startswith(GROUP_PREFIX
):
509 name
= name
[GROUP_PREFIX_LEN
:]
510 return self
._doAddGroup
(name
, roles
, groups
, **kw
)
511 userFolderAddGroup
= postonly(userFolderAddGroup
)
513 security
.declareProtected(Permissions
.manage_users
, "userFolderEditGroup")
514 def userFolderEditGroup(self
, name
, roles
, groups
= None, REQUEST
=None,
516 """API method for changing group object attributes.
518 return self
._doChangeGroup
(name
, roles
= roles
, groups
= groups
, **kw
)
519 userFolderEditGroup
= postonly(userFolderEditGroup
)
521 security
.declareProtected(Permissions
.manage_users
, "userFolderUpdateGroup")
522 def userFolderUpdateGroup(self
, name
, roles
= None, groups
= None,
524 """API method for changing group object attributes.
526 return self
._updateGroup
(name
, roles
= roles
, groups
= groups
, **kw
)
527 userFolderUpdateGroup
= postonly(userFolderUpdateGroup
)
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().
536 return self
._doDelGroups
(names
)
537 userFolderDelUsers
= postonly(userFolderDelUsers
)
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.
557 for src
in self
.listUserSources():
558 # Use source-specific search methods if available
559 if hasattr(src
.aq_base
, "findUser"):
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':
569 Log(LOG_DEBUG
, "we use findUser", attr
, search_term
, )
570 users
= src
.findUser(attr
, search_term
, exact_match
= True)
572 [ u
[id_attr
] for u
in users
],
575 # Other types of user folder
576 search_term
= search_term
.lower()
578 # Find the proper method according to the attribute type
579 if attribute
== "name":
581 elif attribute
== "id":
584 raise NotImplementedError, "Attribute searching is only supported for LDAPUserFolder by now."
587 src_id
= src
.getUserSourceId()
588 for u
in src
.getUsers():
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
)
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)
608 return self
.searchUsersByAttribute("name", search_term
)
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)
619 return self
.searchUsersByAttribute("id", search_term
)
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.
635 # Use source-specific search methods if available
636 if hasattr(src
.aq_base
, "findGroup"):
638 id_attr
= src
._uid
_attr
639 if attribute
== 'name':
640 attr
= src
._login
_attr
641 elif attribute
== 'id':
645 groups
= src
.findGroup(attr
, search_term
)
647 [ u
[id_attr
] for u
in groups
],
650 # Other types of group folder
651 search_term
= search_term
.lower()
653 # Find the proper method according to the attribute type
654 if attribute
== "name":
656 elif attribute
== "id":
659 raise NotImplementedError, "Attribute searching is only supported for LDAPGroupFolder by now."
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())
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)
677 return self
.searchGroupsByAttribute("name", search_term
)
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)
688 return self
.searchGroupsByAttribute("id", search_term
)
691 # SECURITY MANAGEMENT METHODS #
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.
699 self
.userSetRoles(usr
, roles
)
700 setRolesOnUsers
= postonly(setRolesOnUsers
)
702 ## def setUsersOfRole(self, usernames, role):
703 ## """Sets the users of a role.
704 ## XXX THIS METHOD SEEMS TO BE SEAMLESS.
706 ## raise NotImplementedError, "Not implemented."
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
717 for id in self
.getUserIds():
718 if role
in self
.getRolesOfUser(id):
722 security
.declarePublic("getRolesOfUser")
723 def getRolesOfUser(self
, userid
):
724 """Alias for user.getRoles()
726 return self
.getUserById(userid
).getRoles()
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.
732 if role
in self
.aq_parent
.valid_roles():
733 raise ValueError, "Role '%s' already exist" % (role
, )
735 return self
.aq_parent
._addRole
(role
)
736 userFolderAddRole
= postonly(userFolderAddRole
)
738 security
.declareProtected(Permissions
.manage_users
, "userFolderDelRoles")
739 def userFolderDelRoles(self
, roles
, REQUEST
=None):
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.
744 # Check that roles exist
745 ud_roles
= self
.aq_parent
.userdefined_roles()
747 if not r
in ud_roles
:
748 raise ValueError, "Role '%s' is not defined on acl_users' parent folder" % (r
, )
750 # Remove role on all users
752 for u
in self
.getUsersOfRole(r
, ):
753 self
.userRemoveRole(u
, r
, )
755 # Actually remove role
756 return self
.aq_parent
._delRoles
(roles
, None)
757 userFolderDelRoles
= postonly(userFolderDelRoles
)
759 security
.declarePublic("userFolderGetRoles")
760 def userFolderGetRoles(self
, ):
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.
766 return tuple(self
.aq_parent
.valid_roles())
771 security
.declareProtected(Permissions
.manage_users
, "setMembers")
772 def setMembers(self
, groupid
, userids
, REQUEST
=None):
773 """Set the members of the group
775 self
.getGroup(groupid
).setMembers(userids
)
776 setMembers
= postonly(setMembers
)
778 security
.declareProtected(Permissions
.manage_users
, "addMember")
779 def addMember(self
, groupid
, userid
, REQUEST
=None):
780 """Add a member to a group
782 return self
.getGroup(groupid
).addMember(userid
)
783 addMember
= postonly(addMember
)
785 security
.declareProtected(Permissions
.manage_users
, "removeMember")
786 def removeMember(self
, groupid
, userid
, REQUEST
=None):
787 """Remove a member from a group.
789 return self
.getGroup(groupid
).removeMember(userid
)
790 removeMember
= postonly(removeMember
)
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
796 m
= self
.getGroup(groupid
)
798 raise ValueError, "Invalid group: '%s'" % groupid
799 return self
.getGroup(groupid
).getMemberIds()
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
805 return self
.getGroup(groupid
).getUserMemberIds()
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 !
812 return self
.getGroup(groupid
).getGroupMemberIds()
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
820 return self
.getGroup(groupid
).hasMember(id)
825 ## def setUserId(id, newId):
826 ## """Change id of a user atom.
829 ## def setUserName(id, newName):
830 ## """Change the name of a user atom.
833 security
.declareProtected(Permissions
.manage_users
, "userSetRoles")
834 def userSetRoles(self
, id, roles
, REQUEST
=None):
835 """Change the roles of a user atom.
837 self
._updateUser
(id, roles
= roles
)
838 userSetRoles
= postonly(userSetRoles
)
840 security
.declareProtected(Permissions
.manage_users
, "userAddRole")
841 def userAddRole(self
, id, role
, REQUEST
=None):
842 """Append a role for a user atom
844 roles
= list(self
.getUser(id).getRoles())
845 if not role
in roles
:
847 self
._updateUser
(id, roles
= roles
)
848 userAddRole
= postonly(userAddRole
)
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
854 roles
= list(self
.getRolesOfUser(id))
857 self
._updateUser
(id, roles
= roles
)
858 userRemoveRole
= postonly(userRemoveRole
)
860 security
.declareProtected(Permissions
.manage_users
, "userSetPassword")
861 def userSetPassword(self
, id, newPassword
, REQUEST
=None):
862 """Set the password of a user
864 u
= self
.getPureUser(id)
866 raise ValueError, "Invalid pure user id: '%s'" % (id,)
867 self
._updateUser
(u
.getId(), password
= newPassword
, )
868 userSetPassword
= postonly(userSetPassword
)
870 security
.declareProtected(Permissions
.manage_users
, "userGetDomains")
871 def userGetDomains(self
, id):
872 """get domains for a user
874 usr
= self
.getPureUser(id)
875 return tuple(usr
.getDomains())
877 security
.declareProtected(Permissions
.manage_users
, "userSetDomains")
878 def userSetDomains(self
, id, domains
, REQUEST
=None):
879 """Set domains for a user
881 usr
= self
.getPureUser(id)
882 self
._updateUser
(usr
.getId(), domains
= domains
, )
883 userSetDomains
= postonly(userSetDomains
)
885 security
.declareProtected(Permissions
.manage_users
, "userAddDomain")
886 def userAddDomain(self
, id, domain
, REQUEST
=None):
887 """Append a domain to a user
889 usr
= self
.getPureUser(id)
890 domains
= list(usr
.getDomains())
891 if not domain
in domains
:
893 self
._updateUser
(usr
.getId(), domains
= domains
, )
894 userAddDomain
= postonly(userAddDomain
)
896 security
.declareProtected(Permissions
.manage_users
, "userRemoveDomain")
897 def userRemoveDomain(self
, id, domain
, REQUEST
=None):
898 """Remove a domain from a user
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
:
906 self
._updateUser
(usr
.getId(), domains
= domains
)
907 userRemoveDomain
= postonly(userRemoveDomain
)
909 security
.declareProtected(Permissions
.manage_users
, "userSetGroups")
910 def userSetGroups(self
, id, groupnames
, REQUEST
=None):
911 """Set the groups of a user
913 self
._updateUser
(id, groups
= groupnames
)
914 userSetGroups
= postonly(userSetGroups
)
916 security
.declareProtected(Permissions
.manage_users
, "userAddGroup")
917 def userAddGroup(self
, id, groupname
, REQUEST
=None):
918 """add a group to a user atom
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
)
927 security
.declareProtected(Permissions
.manage_users
, "userRemoveGroup")
928 def userRemoveGroup(self
, id, groupname
, REQUEST
=None):
929 """remove a group from a user atom.
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
)
941 # VARIOUS OPERATIONS #
946 __init__(self) -> initialization method
947 We define it to prevend calling ancestor's __init__ methods.
952 security
.declarePrivate('_post_init')
953 def _post_init(self
):
955 _post_init(self) => meant to be called when the
956 object is in the Zope tree
958 uf
= GRUFFolder
.GRUFUsers()
959 gf
= GRUFFolder
.GRUFGroups()
960 self
._setObject
('Users', uf
)
961 self
._setObject
('Groups', gf
)
962 self
.id = "acl_users"
964 def manage_beforeDelete(self
, item
, container
):
966 Special overloading for __allow_groups__ attribute
970 del container
.__allow
_groups
__
974 def manage_afterAdd(self
, item
, container
):
978 container
.__allow
_groups
__ = aq_base(self
)
981 # VARIOUS UTILITIES #
983 # These methods shouldn't be used directly for most applications, #
984 # but they might be useful for some special processing. #
987 security
.declarePublic('getGroupPrefix')
988 def getGroupPrefix(self
):
992 security
.declarePrivate('getGRUFPhysicalRoot')
993 def getGRUFPhysicalRoot(self
,):
994 # $$$ trick meant to be used within
995 # fake_getPhysicalRoot (see __init__)
996 return self
.getPhysicalRoot()
998 security
.declareProtected(Permissions
.view
, 'getGRUFId')
999 def getGRUFId(self
,):
1001 Alias to self.getId()
1005 security
.declareProtected(Permissions
.manage_users
, "getUnwrappedUser")
1006 def getUnwrappedUser(self
, name
):
1008 getUnwrappedUser(self, name) => user object or None
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.
1013 This is useful for UserFolders that define additional User classes,
1014 when you want to call specific methods on these user objects.
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
1021 src_id
= self
.getUser(name
).getUserSourceId()
1022 return self
.getUserSource(src_id
).getUser(name
)
1024 security
.declareProtected(Permissions
.manage_users
, "getUnwrappedGroup")
1025 def getUnwrappedGroup(self
, name
):
1027 getUnwrappedGroup(self, name) => user object or None
1029 Same as getUnwrappedUser but for groups.
1031 return self
.Groups
.acl_users
.getUser(name
)
1034 # AUTHENTICATION INTERFACE #
1037 security
.declarePrivate("authenticate")
1038 def authenticate(self
, name
, password
, request
):
1040 Pass the request along to the underlying user-related UserFolder
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.
1046 # Emergency user checking stuff
1047 emergency
= self
._emergency
_user
1048 if emergency
and name
== emergency
.getUserName():
1049 if emergency
.authenticate(password
, request
):
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
)
1060 return GRUFUser
.GRUFUser(u
, self
, isGroup
= 0, source_id
= src
.getUserSourceId()).__of
__(self
)
1062 # No acl_users in the Users folder or no user authenticated
1063 # => we refuse authentication
1073 security
.declarePrivate("_doAddUser")
1074 def _doAddUser(self
, name
, password
, roles
, domains
, groups
= (), **kw
):
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.
1081 prefix
= GROUP_PREFIX
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
, )
1093 # Reset the users overview batch
1094 self
._v
_batch
_users
= []
1097 return self
.getDefaultUserSource()._doAddUser
(
1104 security
.declarePrivate("_doChangeUser")
1105 def _doChangeUser(self
, name
, password
, roles
, domains
, groups
= None, **kw
):
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.
1112 A None password should not change it (well, we hope so)
1114 # Get actual user name and id
1115 usr
= self
.getUser(name
)
1117 raise ValueError, "Invalid user: '%s'" % (name
,)
1118 id = usr
.getRealId()
1120 # Don't lose existing groups
1122 groups
= usr
.getGroups()
1125 groups
= list(groups
)
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
:
1136 # Reset the users overview batch
1137 self
._v
_batch
_users
= []
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
)
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))
1153 security
.declarePrivate("_updateUser")
1154 def _updateUser(self
, id, password
= None, roles
= None, domains
= None, groups
= None):
1156 _updateUser(self, id, password = None, roles = None, domains = None, groups = None)
1158 This one should work for users AND groups.
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 :-(
1165 # Get the former values if necessary. Username must be valid !
1166 usr
= self
.getUser(id)
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
)
1173 # Check if roles are valid
1174 roles
= filter(lambda x
: x
not in ('Anonymous', 'Authenticated', 'Shared', ''), roles
)
1175 vr
= self
.userFolderGetRoles()
1178 raise ValueError, "Invalid or inexistant role: '%s'." % (r
, )
1180 domains
= usr
._original
_domains
1182 groups
= usr
.getGroups(no_recurse
= 1)
1184 # Check if given groups are valid
1185 glist
= self
.getGroupNames()
1186 glist
.extend(map(lambda x
: "%s%s" % (GROUP_PREFIX
, x
), glist
))
1189 raise ValueError, "Invalid group: '%s'" % (g
, )
1191 # Reset the users overview batch
1192 self
._v
_batch
_users
= []
1195 return self
._doChangeUser
(id, password
, roles
, domains
, groups
)
1197 security
.declarePrivate("_doDelUsers")
1198 def _doDelUsers(self
, names
):
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 !
1204 # Collect information about user sources
1207 usr
= self
.getUser(name
, __include_groups__
= 0)
1209 continue # Ignore invalid user names
1210 src
= usr
.getUserSourceId()
1211 if not sources
.has_key(src
):
1213 sources
[src
].append(name
)
1214 for src
, names
in sources
.items():
1215 self
.getUserSource(src
)._doDelUsers
(names
)
1217 # Reset the users overview batch
1218 self
._v
_batch
_users
= []
1222 # Groups interface #
1225 security
.declarePrivate("_doAddGroup")
1226 def _doAddGroup(self
, name
, roles
, groups
= (), **kw
):
1228 Create a new group. Password will be randomly created, and domain will be None.
1229 Supports nested groups.
1231 # Prepare initial data
1239 for x
in range(0, 10): # Password will be 10 chars long
1240 password
= "%s%s" % (password
, random
.choice(string
.lowercase
), )
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
)
1255 # Reset the users overview batch
1256 self
._v
_batch
_users
= []
1259 return self
.Groups
.acl_users
._doAddUser
(
1260 name
, password
, roles
, domains
, **kw
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
:]
1270 # Check if group exists
1271 grp
= self
.getGroup(name
, prefixed
= 0)
1273 raise ValueError, "Invalid group: '%s'" % (name
,)
1275 # Don't lose existing groups
1277 groups
= grp
.getGroups()
1279 roles
= list(roles
or [])
1280 groups
= list(groups
or [])
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
)
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
:
1298 # Reset the users overview batch
1299 self
._v
_batch
_users
= []
1301 # Perform the change
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
)
1309 security
.declarePrivate("_updateGroup")
1310 def _updateGroup(self
, name
, roles
= None, groups
= None):
1312 _updateGroup(self, name, roles = None, groups = None)
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.
1319 group name can be prefixed or not
1321 # Remove prefix if given
1322 if name
.startswith(self
.getGroupPrefix()):
1323 name
= name
[GROUP_PREFIX_LEN
:]
1325 # Get the former values if necessary. Username must be valid !
1326 usr
= self
.getGroup(name
, prefixed
= 0)
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
)
1333 groups
= usr
.getGroups(no_recurse
= 1)
1335 # Reset the users overview batch
1336 self
._v
_batch
_users
= []
1339 return self
._doChangeGroup
(name
, roles
, groups
)
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
:]
1349 # Reset the users overview batch
1350 self
._v
_batch
_users
= []
1353 return self
.Groups
.acl_users
._doDelUsers
([name
])
1355 security
.declarePrivate("_doDelGroups")
1356 def _doDelGroups(self
, names
):
1357 """Delete one or more users."""
1359 if not self
.getGroupByName(group
, None):
1360 continue # Ignore invalid groups
1361 self
._doDelGroup
(group
)
1367 # Pretty Management form methods #
1371 security
.declarePublic('getGRUFVersion')
1372 def getGRUFVersion(self
,):
1374 getGRUFVersion(self,) => Return human-readable GRUF version as a string.
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
)
1380 reset_entry
= "__None__" # Special entry used for reset
1382 security
.declareProtected(Permissions
.manage_users
, "changeUser")
1383 def changeUser(self
, user
, groups
= [], roles
= [], REQUEST
= {}, ):
1385 changeUser(self, user, groups = [], roles = [], REQUEST = {}, ) => used in ZMI
1387 obj
= self
.getUser(user
)
1389 self
._updateGroup
(name
= user
, groups
= groups
, roles
= roles
, )
1391 self
._updateUser
(id = user
, groups
= groups
, roles
= roles
, )
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
)
1398 security
.declareProtected(Permissions
.manage_users
, "deleteUser")
1399 def deleteUser(self
, user
, REQUEST
= {}, ):
1401 deleteUser(self, user, REQUEST = {}, ) => used in ZMI
1404 deleteUser
= postonly(deleteUser
)
1406 security
.declareProtected(Permissions
.manage_users
, "changeOrCreateUsers")
1407 def changeOrCreateUsers(self
, users
= [], groups
= [], roles
= [], new_users
= [], default_password
= '', REQUEST
= {}, ):
1409 changeOrCreateUsers => affect roles & groups to users and/or create new users
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.
1414 # Manage roles / groups deletion
1417 if self
.reset_entry
in roles
:
1418 roles
.remove(self
.reset_entry
)
1420 if self
.reset_entry
in groups
:
1421 groups
.remove(self
.reset_entry
)
1423 if not roles
and not del_roles
:
1424 roles
= None # None instead of [] to avoid deletion
1428 if not groups
and not del_groups
:
1434 # Passwords management
1437 # Create brand new users
1438 for new
in new_users
:
1440 name
= string
.strip(new
)
1444 # Avoid erasing former users
1445 if name
in map(lambda x
: x
.getId(), self
.getUsers()):
1448 # Use default password or generate a random one
1449 if default_password
:
1450 password
= default_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
, )
1457 # Store the newly created password
1458 passwords_list
.append({'name':name
, 'password':password
})
1460 # Update existing users
1462 self
._updateUser
(id = user
, groups
= groups
, roles
= roles
, )
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")
1470 # Show passwords form
1472 REQUEST
.set('USER_PASSWORDS', passwords_list
)
1473 return self
.manage_newusers(None, self
)
1475 # Simply return the list of created passwords
1476 return passwords_list
1477 changeOrCreateUsers
= postonly(changeOrCreateUsers
)
1479 security
.declareProtected(Permissions
.manage_users
, "deleteUsers")
1480 def deleteUsers(self
, users
= [], REQUEST
= {}):
1482 deleteUsers => explicit
1484 All parameters are strings. NO CHECKING IS DONE. This is an utility method !
1487 self
._doDelUsers
(users
, )
1490 if REQUEST
.has_key('RESPONSE'):
1491 return REQUEST
.RESPONSE
.redirect(self
.absolute_url() + "/manage_users")
1492 deleteUsers
= postonly(deleteUsers
)
1494 security
.declareProtected(Permissions
.manage_users
, "changeOrCreateGroups")
1495 def changeOrCreateGroups(self
, groups
= [], roles
= [], nested_groups
= [], new_groups
= [], REQUEST
= {}, ):
1497 changeOrCreateGroups => affect roles to groups and/or create new groups
1499 All parameters are strings. NO CHECKING IS DONE. This is an utility method !
1501 # Manage roles / groups deletion
1504 if self
.reset_entry
in roles
:
1505 roles
.remove(self
.reset_entry
)
1507 if self
.reset_entry
in nested_groups
:
1508 nested_groups
.remove(self
.reset_entry
)
1510 if not roles
and not del_roles
:
1511 roles
= None # None instead of [] to avoid deletion
1515 if not nested_groups
and not del_groups
:
1516 nested_groups
= None
1519 add_groups
= nested_groups
1521 # Create brand new groups
1522 for new
in new_groups
:
1523 name
= string
.strip(new
)
1526 self
._doAddGroup
(name
, roles
, groups
= add_groups
)
1528 # Update existing groups
1529 for group
in groups
:
1530 self
._updateGroup
(group
, roles
= roles
, groups
= nested_groups
)
1533 if REQUEST
.has_key('RESPONSE'):
1534 return REQUEST
.RESPONSE
.redirect(self
.absolute_url() + "/manage_groups")
1535 changeOrCreateGroups
= postonly(changeOrCreateGroups
)
1537 security
.declareProtected(Permissions
.manage_users
, "deleteGroups")
1538 def deleteGroups(self
, groups
= [], REQUEST
= {}):
1540 deleteGroups => explicit
1542 All parameters are strings. NO CHECKING IS DONE. This is an utility method !
1545 for group
in groups
:
1546 self
._doDelGroup
(group
, )
1549 if REQUEST
.has_key('RESPONSE'):
1550 return REQUEST
.RESPONSE
.redirect(self
.absolute_url() + "/manage_groups")
1551 deleteGroups
= postonly(deleteGroups
)
1554 # Local Roles Acquisition Blocking #
1555 # Those two methods perform their own security check. #
1558 security
.declarePublic("acquireLocalRoles")
1559 def acquireLocalRoles(self
, folder
, status
, REQUEST
=None):
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 ! :^)
1570 # Perform security check on destination folder
1571 if not getSecurityManager().checkPermission(Permissions
.change_permissions
, folder
):
1572 raise Unauthorized(name
= "acquireLocalRoles")
1574 return self
._acquireLocalRoles
(folder
, status
)
1575 acquireLocalRoles
= postonly(acquireLocalRoles
)
1577 def _acquireLocalRoles(self
, folder
, status
):
1578 """Same as _acquireLocalRoles() but won't perform security check on the folder.
1580 # Set the variable (or unset it if it's defined)
1582 folder
.__ac
_local
_roles
_block
__ = 1
1584 if getattr(folder
, '__ac_local_roles_block__', None):
1585 folder
.__ac
_local
_roles
_block
__ = None
1588 security
.declarePublic("isLocalRoleAcquired")
1589 def isLocalRoleAcquired(self
, folder
):
1590 """Return true if the specified folder allows local role acquisition.
1592 if getattr(folder
, '__ac_local_roles_block__', None):
1598 # Security audit and info methods #
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")
1620 return self
._getLocalRolesForDisplay
(object)
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)"""
1626 local_roles
= object.get_local_roles()
1627 prefix
= self
.getGroupPrefix()
1628 for one_user
in local_roles
:
1629 massagedUsername
= username
= one_user
[0]
1633 if self
.getGroupById(username
) is not None:
1634 massagedUsername
= username
[len(prefix
):]
1637 userType
= 'unknown'
1638 result
.append((massagedUsername
, roles
, userType
, username
))
1639 return tuple(result
)
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
1648 # Perform security check on destination object
1649 if not getSecurityManager().checkPermission(Permissions
.change_permissions
, object):
1650 raise Unauthorized(name
= "getAllLocalRoles")
1652 return self
._getAllLocalRoles
(object)
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
1660 # Modified from AccessControl.User.getRolesInContext().
1662 object = getattr(object, 'aq_inner', object)
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
):
1672 if not self
.isLocalRoleAcquired(object):
1674 if hasattr(object, 'aq_parent'):
1675 object=object.aq_parent
1676 object=getattr(object, 'aq_inner', object)
1678 if hasattr(object, 'im_self'):
1679 object=object.im_self
1680 object=getattr(object, 'aq_inner', object)
1683 for key
, value
in merged
.items():
1684 merged
[key
] = value
.keys()
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:
1696 depth: (0 for portal root, 1 for 1st-level folders and so on),
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
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
1717 # Perform security check on destination object
1718 if not getSecurityManager().checkPermission(Permissions
.access_contents_information
, object):
1719 raise Unauthorized(name
= "getPloneSecurityMatrix")
1722 mt
= self
.portal_membership
1724 # Fetch all possible roles in the portal
1725 all_roles
= ['Anonymous'] + mt
.getPortalRoles()
1727 # Fetch parent folders list until the portal
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":
1736 cur_object
= object.aq_parent
1737 all_objects
.reverse()
1739 # Scan those folders to get all the required information about them
1743 for obj
in all_objects
:
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
),
1754 "state": "XXX TODO XXX", # XXX TODO
1762 all_local_roles
= {}
1763 local_roles
= self
._getAllLocalRoles
(obj
)
1764 for user
, roles
in self
._getAllLocalRoles
(obj
).items():
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():
1773 if not defined_local_roles
.has_key(role
):
1774 defined_local_roles
[role
] = {}
1775 defined_local_roles
[role
][user
] = 1
1777 for role
in all_roles
:
1778 all
= all_local_roles
.get(role
, {}).keys()
1779 defined
= defined_local_roles
.get(role
, {}).keys()
1782 same_all_local_roles
= 0
1783 same_defined_local_roles
= 0
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
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
1804 security
.declareProtected(Permissions
.manage_users
, "computeSecuritySettings")
1805 def computeSecuritySettings(self
, folders
, actors
, permissions
, cache
= {}):
1807 computeSecuritySettings(self, folders, actors, permissions, cache = {}) => return a structure that is suitable for security audit Page Template.
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
1814 # Scan folders and actors to get the relevant information
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"):
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
)] = {}
1826 cache
[path
][(kind
, actor
)] = {}
1828 # Split kind into groups and get individual role information
1830 usr
= usr_cache
.get(actor
)
1832 usr
= self
.getUser(actor
)
1833 usr_cache
[actor
] = usr
1834 roles
= usr
.getRolesInContext(folder
,)
1836 for perm_key
in self
.computeSetting(path
, folder
, role
, permissions
, cache
).keys():
1837 cache
[path
][(kind
, actor
)][perm_key
] = 1
1840 # Get role information
1841 self
.computeSetting(path
, folder
, actor
, permissions
, cache
)
1843 # Return the computed cache
1847 security
.declareProtected(Permissions
.manage_users
, "computeSetting")
1848 def computeSetting(self
, path
, folder
, actor
, permissions
, cache
):
1850 computeSetting(......) => used by computeSecuritySettings to populate the cache for ROLES
1852 # Avoid doing things twice
1854 if cache
.get(path
, {}).get((kind
, actor
), None) is not None:
1855 return cache
[path
][(kind
, actor
)]
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
)] = {}
1863 # Analyze permission settings
1864 ps
= folder
.permission_settings()
1865 for perm_key
, permission
in permissions
:
1866 # Check acquisition of permission setting.
1870 if p
['name'] == permission
:
1871 acquired
= not not p
['acquire']
1873 # If acquired, call the parent recursively
1875 parent
= folder
.aq_parent
.getPhysicalPath()
1876 perms
= self
.computeSetting(parent
, self
.unrestrictedTraverse(parent
), actor
, permissions
, cache
)
1877 can
= perms
.get(perm_key
, None)
1879 # Else, check permission here
1881 for p
in folder
.rolesOfPermission(permission
):
1882 if p
['name'] == "Anonymous":
1883 # If anonymous is allowed, then everyone is allowed
1887 if p
['name'] == actor
:
1892 # Extend the data structure according to 'can' setting
1894 cache
[path
][(kind
, actor
)][perm_key
] = 1
1896 return cache
[path
][(kind
, actor
)]
1899 security
.declarePrivate('_getNextHandle')
1900 def _getNextHandle(self
, index
):
1902 _getNextHandle(self, index) => utility function to
1903 get an unique handle for each legend item.
1905 return "%02d" % index
1908 security
.declareProtected(Permissions
.manage_users
, "listUsersAndRoles")
1909 def listUsersAndRoles(self
,):
1911 listUsersAndRoles(self,) => list of tuples
1913 This method is used by the Security Audit page.
1914 XXX HAS TO BE OPTIMIZED
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)
1928 for r
in self
.aq_parent
.valid_roles():
1929 handle
= "R%02d" % role_index
1931 ret
.append(('role', r
, r
, handle
, r
))
1935 for u
in map(lambda x
: x
.getId(), self
.getPureUsers()):
1936 obj
= self
.getUser(u
)
1938 handle
= "U%02d" % user_index
1940 ret
.append(('user', u
, u
, handle
, html
))
1943 for u
in self
.getGroupNames():
1944 obj
= self
.getUser(u
)
1945 handle
= "G%02d" % group_index
1948 ret
.append(('group', u
, obj
.getUserNameWithoutGroupPrefix(), handle
, html
))
1953 security
.declareProtected(Permissions
.manage_users
, "getSiteTree")
1954 def getSiteTree(self
, obj
=None, depth
=0):
1956 getSiteTree(self, obj=None, depth=0) => special structure
1958 This is used by the security audit page
1963 obj
= self
.aq_parent
1967 ret
.append([obj
.getId(), depth
, string
.join(obj
.getPhysicalPath(), '/')])
1968 for sub
in obj
.objectValues():
1970 # Ignore user folders
1971 if sub
.getId() in ('acl_users', ):
1974 # Ignore portal_* stuff
1975 if sub
.getId()[:len('portal_')] == 'portal_':
1978 if sub
.isPrincipiaFolderish
:
1979 ret
.extend(self
.getSiteTree(sub
, depth
+ 1))
1982 # We ignore exceptions
1987 security
.declareProtected(Permissions
.manage_users
, "listAuditPermissions")
1988 def listAuditPermissions(self
,):
1990 listAuditPermissions(self,) => return a list of eligible permissions
1992 ps
= self
.permission_settings()
1993 return map(lambda p
: p
['name'], ps
)
1995 security
.declareProtected(Permissions
.manage_users
, "getDefaultPermissions")
1996 def getDefaultPermissions(self
,):
1998 getDefaultPermissions(self,) => return default R & W permissions for security audit.
2000 # If there's a Plone site in the above folder, use plonish permissions
2003 if p
.meta_type
== "CMF Site":
2006 for obj
in p
.objectValues():
2007 if obj
.meta_type
== "CMF Site":
2012 return {'R': 'View',
2013 'W': 'Modify portal content',
2016 return {'R': 'View',
2017 'W': 'Change Images and Files',
2022 # Users/Groups tree view #
2027 security
.declarePrivate('getTreeInfo')
2028 def getTreeInfo(self
, usr
, dict = {}):
2030 # Prevend infinite recursions
2031 name
= usr
.getUserName()
2032 if dict.has_key(name
):
2037 noprefix
= usr
.getUserNameWithoutGroupPrefix()
2038 is_group
= usr
.isGroup()
2040 icon
= string
.join(self
.getPhysicalPath(), '/') + '/img_group'
2041 ## icon = self.absolute_url() + '/img_group'
2044 ## icon = self.absolute_url() + '/img_user'
2048 for grp
in usr
.getGroups(no_recurse
= 1):
2049 belongs_to
.append(grp
)
2050 self
.getTreeInfo(self
.getGroup(grp
))
2052 # Append (and return) structure
2055 "is_group": is_group
,
2057 "belongs_to": belongs_to
,
2062 security
.declarePrivate("tpValues")
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
2069 # XXX - I DISABLE THE TREE BY NOW (Pb. with icon URL)
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
:
2076 self
._v
_cache
_no
_tree
= time
.time() + TREE_CACHE_TIME
2078 nusers
= len(self
.getUsers())
2079 if ngroups
+ nusers
> MAX_TREE_USERS_AND_GROUPS
:
2080 meth_list
= self
.getGroups
2082 meth_list
= self
.getUsers
2085 # Get top-level user and groups list
2087 top_level_names
= []
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
))
2096 # Return this top-level list
2097 top_level
.sort(lambda x
, y
: cmp(x
.sortId(), y
.sortId()))
2106 # Direct traversal to user or group info #
2109 def manage_workspace(self
, REQUEST
):
2111 manage_workspace(self, REQUEST) => Overrided to allow direct user or group traversal
2112 via the left tree view.
2114 path
= string
.split(REQUEST
.PATH_INFO
, '/')[:-1]
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
)
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')()
2125 # Default management screen
2126 return self
.restrictedTraverse('manage_overview')()
2129 # Tree caching information
2131 _v_cache_no_tree
= 0
2132 _v_cache_tree
= (0, [])
2135 def __bobo_traverse__(self
, request
, name
):
2137 Looks for the name of a user or a group.
2138 This applies only if users list is not huge.
2140 # Check if it's an attribute
2141 if hasattr(self
.aq_base
, name
, ):
2142 return getattr(self
, name
)
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
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
, )
2160 un
= self
._v
_cache
_tree
[1]
2162 # Get the user if we can
2167 # Force getting the user if we must
2168 if request
.get("FORCE_USER"):
2172 # This will raise if it's not possible to acquire 'name'
2173 return getattr(self
, name
, )
2178 # USERS / GROUPS BATCHING (ZMI SCREENS) #
2183 security
.declareProtected(Permissions
.view_management_screens
, "listUsersBatches")
2184 def listUsersBatches(self
,):
2186 listUsersBatches(self,) => return a list of (start, end) tuples.
2187 Return None if batching is not necessary
2189 # Time-consuming stuff !
2190 un
= map(lambda x
: x
.getId(), self
.getPureUsers())
2191 if len(un
) <= MAX_USERS_PER_PAGE
:
2195 # Split this list into small groups if necessary
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
2205 # Append a tuple (not dict) to avoid too much memory consumption
2206 ret
.append((first
, last
, un
[first
], un
[last
]))
2209 self
._v
_batch
_users
= un
2212 security
.declareProtected(Permissions
.view_management_screens
, "listUsersBatchTable")
2213 def listUsersBatchTable(self
,):
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
2223 for rec
in (self
.listUsersBatches() or []):
2236 security
.declareProtected(Permissions
.view_management_screens
, "getUsersBatch")
2237 def getUsersBatch(self
, start
):
2239 getUsersBatch(self, start) => user list
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
2247 end
= start
+ MAX_USERS_PER_PAGE
2248 ids
= self
._v
_batch
_users
[start
:end
]
2251 usr
= self
.getUser(id)
2252 if usr
: # Prevent adding invalid users
2258 # Multiple sources management #
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())
2267 security
.declareProtected(Permissions
.manage_users
, "toggleSource")
2268 def toggleSource(self
, src_id
, REQUEST
= {}):
2270 toggleSource(self, src_id, REQUEST = {}) => toggle enabled/disabled 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
)
2282 # Redirect where we want to
2283 if REQUEST
.has_key('RESPONSE'):
2284 return REQUEST
.RESPONSE
.redirect(self
.absolute_url() + '/manage_GRUFSources')
2287 security
.declareProtected(Permissions
.manage_users
, "listUserSources")
2288 def listUserSources(self
, ):
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
2295 This method return only _enabled_ user sources.
2300 for src
in self
.objectValues(['GRUFUsers']):
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.
2308 for src
in self
.objectValues(['GRUFUsers']):
2311 if not 'acl_users' in src
.objectIds():
2313 dret
[src
.id] = src
.acl_users
2316 return [ src
[1] for src
in ret
]
2318 security
.declareProtected(Permissions
.manage_users
, "listUserSourceFolders")
2319 def listUserSourceFolders(self
, ):
2321 listUserSources(self, ) => Return a list of GRUFUsers objects
2324 for src
in self
.objectValues(['GRUFUsers']):
2326 ret
.sort(lambda x
,y
: cmp(x
.id, y
.id))
2329 security
.declarePrivate("getUserSource")
2330 def getUserSource(self
, id):
2332 getUserSource(self, id) => GRUFUsers.acl_users object.
2333 Raises if no acl_users available
2335 return getattr(self
, id).acl_users
2337 security
.declarePrivate("getUserSourceFolder")
2338 def getUserSourceFolder(self
, id):
2340 getUserSourceFolder(self, id) => GRUFUsers object
2342 return getattr(self
, id)
2344 security
.declareProtected(Permissions
.manage_users
, "addUserSource")
2345 def addUserSource(self
, factory_uri
, REQUEST
= {}, *args
, **kw
):
2347 addUserSource(self, factory_uri, REQUEST = {}, *args, **kw) => redirect
2348 Adds the specified user folder
2350 # Get the initial Users id
2351 ids
= self
.objectIds('GRUFUsers')
2354 if ids
== ['Users',]:
2357 last
= int(ids
[-1][-2:])
2358 next_id
= "Users%02d" % (last
+ 1, )
2362 # Add the GRUFFolder object
2363 uf
= GRUFFolder
.GRUFUsers(id = next_id
)
2364 self
._setObject
(next_id
, uf
)
2366 ## # If we use ldap, tag it
2367 ## if string.find(factory_uri.lower(), "ldap") > -1:
2368 ## self._haveLDAPUF += 1
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
)
2377 security
.declareProtected(Permissions
.manage_users
, "deleteUserSource")
2378 def deleteUserSource(self
, id = None, REQUEST
= {}):
2380 deleteUserSource(self, id = None, REQUEST = {}) => Delete the specified user source
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."
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
)
2392 security
.declareProtected(Permissions
.manage_users
, "getDefaultUserSource")
2393 def getDefaultUserSource(self
,):
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.
2399 lst
= self
.listUserSources()
2401 raise RuntimeError, "No valid User Source to add users in."
2405 security
.declareProtected(Permissions
.manage_users
, "listAvailableUserSources")
2406 def listAvailableUserSources(self
, filter_permissions
= 1, filter_classes
= 1):
2408 listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1) => tuples (name, factory_uri)
2409 List UserFolder replacement candidates.
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
2416 # Fetch candidate types
2417 user
= getSecurityManager().getUser()
2419 if callable(self
.all_meta_types
):
2420 all
=self
.all_meta_types()
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
)
2428 meta_types
.append(meta_type
)
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 ! ;-)
2437 if t
.get('instance', None) and t
['instance'].isAUserFolder
:
2438 ret
.append((t
['name'], t
['action']))
2440 if t
.get('instance', None) and class_utility
.isBaseClass(AccessControl
.User
.BasicUserFolder
, t
['instance']):
2441 ret
.append((t
['name'], t
['action']))
2443 except AttributeError:
2444 pass # We ignore 'invalid' instances (ie. that wouldn't define a __base__ attribute)
2446 ret
.append((t
['name'], t
['action']))
2450 security
.declareProtected(Permissions
.manage_users
, "moveUserSourceUp")
2451 def moveUserSourceUp(self
, id, REQUEST
= {}):
2453 moveUserSourceUp(self, id, REQUEST = {}) => used in management screens
2454 try to get ids as consistant as possible
2456 # List and sort sources and preliminary checks
2457 ids
= self
.objectIds('GRUFUsers')
2459 if not ids
or not id in ids
:
2460 raise ValueError, "Invalid User Source: '%s'" % (id,)
2462 # Find indexes to swap
2463 src_index
= ids
.index(id)
2465 raise ValueError, "Cannot move '%s' User Source up." % (id, )
2466 dest_index
= src_index
- 1
2468 # Find numbers to swap, fix them if they have more than 1 as offset
2469 if ids
[dest_index
] == 'Users':
2472 dest_num
= int(ids
[dest_index
][-2:])
2473 src_num
= dest_num
+ 1
2480 dest_id
= "Users%02d" % (dest_num
,)
2481 tmp_id
= "%s_" % (dest_id
, )
2484 self
._renameUserSource
(src_id
, tmp_id
)
2485 self
._renameUserSource
(dest_id
, src_id
)
2486 self
._renameUserSource
(tmp_id
, dest_id
)
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
)
2493 security
.declareProtected(Permissions
.manage_users
, "moveUserSourceDown")
2494 def moveUserSourceDown(self
, id, REQUEST
= {}):
2496 moveUserSourceDown(self, id, REQUEST = {}) => used in management screens
2497 try to get ids as consistant as possible
2499 # List and sort sources and preliminary checks
2500 ids
= self
.objectIds('GRUFUsers')
2502 if not ids
or not id in ids
:
2503 raise ValueError, "Invalid User Source: '%s'" % (id,)
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
2511 # Find numbers to swap, fix them if they have more than 1 as offset
2515 dest_num
= int(ids
[dest_index
][-2:])
2516 src_num
= dest_num
- 1
2523 dest_id
= "Users%02d" % (dest_num
,)
2524 tmp_id
= "%s_" % (dest_id
, )
2527 self
._renameUserSource
(src_id
, tmp_id
)
2528 self
._renameUserSource
(dest_id
, src_id
)
2529 self
._renameUserSource
(tmp_id
, dest_id
)
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
)
2537 security
.declarePrivate('_renameUserSource')
2538 def _renameUserSource(self
, id, new_id
, ):
2540 Rename a particular sub-object.
2541 Taken fro CopySupport.manage_renameObject() code, modified to disable verifications.
2543 try: self
._checkId
(new_id
)
2544 except: raise CopyError
, MessageDialog(
2546 message
=sys
.exc_info()[1],
2547 action
='manage_main')
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')
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)
2566 security
.declareProtected(Permissions
.manage_users
, "replaceUserSource")
2567 def replaceUserSource(self
, id = None, new_factory
= None, REQUEST
= {}, *args
, **kw
):
2569 replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw) => perform user source replacement
2571 If new_factory is None, find it inside REQUEST (useful for ZMI screens)
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."
2577 # Retreive factory if not explicitly passed
2579 for record
in REQUEST
.get("source_rec", []):
2580 if record
['id'] == id:
2581 new_factory
= record
['new_factory']
2584 raise ValueError, "You must select a new User Folder type."
2586 # Delete the former one
2587 us
= getattr(self
, id)
2588 if "acl_users" in us
.objectIds():
2589 us
.manage_delObjects(['acl_users'])
2591 ## If we use ldap, tag it
2592 #if string.find(new_factory.lower(), "ldap") > -1:
2593 # self._haveLDAPUF += 1
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
)
2603 security
.declareProtected(Permissions
.manage_users
, "hasLDAPUserFolderSource")
2604 def hasLDAPUserFolderSource(self
, ):
2606 hasLDAPUserFolderSource(self,) => boolean
2607 Return true if a LUF source is instanciated.
2609 for src
in self
.listUserSources():
2610 if src
.meta_type
== "LDAPUserFolder":
2615 security
.declareProtected(Permissions
.manage_users
, "updateLDAPUserFolderMapping")
2616 def updateLDAPUserFolderMapping(self
, REQUEST
= None):
2618 updateLDAPUserFolderMapping(self, REQUEST = None) => None
2620 Update the first LUF source in the process so that LDAP-group-to-Zope-role mapping
2622 This is done by calling the appropriate method in LUF and affecting all 'group_' roles
2623 to the matching LDAP groups.
2626 groups
= self
.getGroupIds()
2629 for src
in self
.listUserSources():
2630 if not src
.meta_type
== "LDAPUserFolder":
2633 # Delete all former group mappings
2635 for (grp
, role
) in src
.getGroupMappings():
2636 if role
.startswith('group_'):
2638 src
.manage_deleteGroupMappings(deletes
)
2640 # Append all group mappings if it can be done
2641 ldap_groups
= src
.getGroups(attr
= "cn")
2643 if src
._local
_groups
:
2646 grp_name
= grp
[len('group_'):]
2647 Log(LOG_DEBUG
, "cheching", grp_name
, "in", ldap_groups
, )
2648 if not grp_name
in ldap_groups
:
2650 Log(LOG_DEBUG
, "Map", grp
, "to", grp_name
)
2651 src
.manage_addGroupMapping(
2658 return REQUEST
.RESPONSE
.redirect(
2659 self
.absolute_url() + "/manage_wizard",
2661 updateLDAPUserFolderMapping
= postonly(updateLDAPUserFolderMapping
)
2665 # The Wizard Section #
2668 def listLDAPUserFolderMapping(self
,):
2670 listLDAPUserFolderMapping(self,) => utility method
2677 for src
in self
.listUserSources():
2678 if not src
.meta_type
== "LDAPUserFolder":
2681 # Get all GRUF & LDAP groups
2682 if src
._local
_groups
:
2683 gruf_ids
= self
.getGroupIds()
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
)
2705 security
.declareProtected(Permissions
.manage_users
, "getInvalidMappings")
2706 def getInvalidMappings(self
,):
2708 return true if LUF mapping looks good
2712 for gruf
, ldap
in self
.listLDAPUserFolderMapping():
2717 if gruf
.startswith('group_'):
2718 gruf
= gruf
[len('group_'):]
2720 for gruf
, ldap
in self
.listLDAPUserFolderMapping():
2725 if ldap
.startswith('group_'):
2726 ldap
= ldap
[len('group_'):]
2732 security
.declareProtected(Permissions
.manage_users
, "getLUFSource")
2733 def getLUFSource(self
,):
2735 getLUFSource(self,) => Helper to get a pointer to the LUF src.
2736 Return None if not available
2738 for src
in self
.listUserSources():
2739 if src
.meta_type
== "LDAPUserFolder":
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')
2748 security
.declareProtected(Permissions
.manage_users
, "haveLDAPGroupFolder")
2749 def haveLDAPGroupFolder(self
,):
2750 """return true if LDAPGroupFolder is the groups source
2752 return not not self
.Groups
.acl_users
.meta_type
== 'LDAPGroupFolder'
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
]
2763 treeWrapper: Wrapper around user/group objects for the tree
2765 def __init__(self
, id, tree
, parents
= []):
2767 __init__(self, id, tree, parents = []) => wraps the user object for dtml-tree
2769 # Prepare self-contained information
2771 self
.name
= tree
[id]['name']
2772 self
.icon
= tree
[id]['icon']
2773 self
.is_group
= tree
[id]['is_group']
2777 # Prepare subobjects information
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
2790 return "__%s" % (self
._id
,)
2794 def tpValues(self
,):
2798 return self
.subobjects
2806 InitializeClass(GroupUserFolder
)