Back to index

plone3  3.1.7
GroupUserFolder.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 ## GroupUserFolder
00003 ## Copyright (C)2006 Ingeniweb
00004 
00005 ## This program is free software; you can redistribute it and/or modify
00006 ## it under the terms of the GNU General Public License as published by
00007 ## the Free Software Foundation; either version 2 of the License, or
00008 ## (at your option) any later version.
00009 
00010 ## This program is distributed in the hope that it will be useful,
00011 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 ## GNU General Public License for more details.
00014 
00015 ## You should have received a copy of the GNU General Public License
00016 ## along with this program; see the file COPYING. If not, write to the
00017 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00018 """
00019 GroupUserFolder product
00020 """
00021 __version__ = "$Revision:  $"
00022 # $Source:  $
00023 # $Id: GroupUserFolder.py 40118 2007-04-01 15:13:44Z alecm $
00024 __docformat__ = 'restructuredtext'
00025 
00026 
00027 # fakes a method from a DTML file
00028 from Globals import MessageDialog, DTMLFile
00029 
00030 from AccessControl import ClassSecurityInfo
00031 from AccessControl import Permissions
00032 from AccessControl import getSecurityManager
00033 from AccessControl import Unauthorized
00034 from Globals import InitializeClass
00035 from Acquisition import aq_base, aq_inner, aq_parent
00036 from Acquisition import Implicit
00037 from Globals import Persistent
00038 from AccessControl.Role import RoleManager
00039 from OFS.SimpleItem import Item
00040 from OFS.PropertyManager import PropertyManager
00041 import OFS
00042 from OFS import ObjectManager, SimpleItem
00043 from DateTime import DateTime
00044 from App import ImageFile
00045 from Products.PageTemplates import PageTemplateFile
00046 import AccessControl.Role, webdav.Collection
00047 import Products
00048 import os
00049 import string
00050 import sys
00051 import time
00052 import math
00053 import random
00054 from global_symbols import *
00055 import AccessControl.User
00056 import GRUFFolder
00057 import GRUFUser
00058 from Products.PageTemplates import PageTemplateFile
00059 import class_utility
00060 from Products.GroupUserFolder import postonly
00061 
00062 from interfaces.IUserFolder import IUserFolder
00063 
00064 ## Developers notes
00065 ##
00066 ## The REQUEST.GRUF_PROBLEM variable is defined whenever GRUF encounters
00067 ## a problem than can be showed in the management screens. It's always
00068 ## logged as LOG_WARNING level anyway.
00069 
00070 _marker = []
00071 
00072 def unique(sequence, _list = 0):
00073     """Make a sequence a list of unique items"""
00074     uniquedict = {}
00075     for v in sequence:
00076         uniquedict[v] = 1
00077     if _list:
00078         return list(uniquedict.keys())
00079     return tuple(uniquedict.keys())
00080 
00081 
00082 def manage_addGroupUserFolder(self, dtself=None, REQUEST=None, **ignored):
00083     """ Factory method that creates a UserFolder"""
00084     f=GroupUserFolder()
00085     self=self.this()
00086     try:    self._setObject('acl_users', f)
00087     except: return MessageDialog(
00088                    title  ='Item Exists',
00089                    message='This object already contains a User Folder',
00090                    action ='%s/manage_main' % REQUEST['URL1'])
00091     self.__allow_groups__=f
00092     self.acl_users._post_init()
00093 
00094     self.acl_users.Users.manage_addUserFolder()
00095     self.acl_users.Groups.manage_addUserFolder()
00096 
00097     if REQUEST is not None:
00098         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00099 
00100 
00101 
00102 
00103 class GroupUserFolder(OFS.ObjectManager.ObjectManager,
00104                       AccessControl.User.BasicUserFolder,
00105                       ):
00106     """
00107     GroupUserFolder => User folder with groups management
00108     """
00109 
00110     #                                                                           #
00111     #                              ZOPE  INFORMATION                            #
00112     #                                                                           #
00113 
00114     meta_type='Group User Folder'
00115     id       ='acl_users'
00116     title    ='Group-aware User Folder'
00117 
00118     __implements__ = (IUserFolder, )
00119     def __creatable_by_emergency_user__(self): return 1
00120 
00121     isAnObjectManager = 1
00122     isPrincipiaFolderish = 1
00123     isAUserFolder = 1
00124 
00125 ##    _haveLDAPUF = 0
00126 
00127     security = ClassSecurityInfo()
00128 
00129     manage_options=(
00130         (
00131         {'label':'Overview', 'action':'manage_overview'},
00132         {'label':'Sources', 'action':'manage_GRUFSources'},
00133         {'label':'LDAP Wizard', 'action':'manage_wizard'},
00134         {'label':'Groups', 'action':'manage_groups'},
00135         {'label':'Users', 'action':'manage_users'},
00136         {'label':'Audit', 'action':'manage_audit'},
00137         ) + \
00138         OFS.ObjectManager.ObjectManager.manage_options + \
00139         RoleManager.manage_options + \
00140         Item.manage_options )
00141 
00142     manage_main = OFS.ObjectManager.ObjectManager.manage_main
00143 ##    manage_overview = DTMLFile('dtml/GRUF_overview', globals())
00144     manage_overview = PageTemplateFile.PageTemplateFile('dtml/GRUF_overview', globals())
00145     manage_audit = PageTemplateFile.PageTemplateFile('dtml/GRUF_audit', globals())
00146     manage_wizard = PageTemplateFile.PageTemplateFile('dtml/GRUF_wizard', globals())
00147     manage_groups = PageTemplateFile.PageTemplateFile('dtml/GRUF_groups', globals())
00148     manage_users = PageTemplateFile.PageTemplateFile('dtml/GRUF_users', globals())
00149     manage_newusers = PageTemplateFile.PageTemplateFile('dtml/GRUF_newusers', globals())
00150     manage_GRUFSources = PageTemplateFile.PageTemplateFile('dtml/GRUF_contents', globals())
00151     manage_user = PageTemplateFile.PageTemplateFile('dtml/GRUF_user', globals())
00152 
00153     __ac_permissions__=(
00154         ('Manage users',
00155          ('manage_users',
00156           'user_names', 'setDomainAuthenticationMode',
00157           )
00158          ),
00159         )
00160 
00161 
00162     # Color constants, only useful within GRUF management screens
00163     user_color = "#006600"
00164     group_color = "#000099"
00165     role_color = "#660000"
00166 
00167     # User and group images
00168     img_user = ImageFile.ImageFile('www/GRUFUsers.gif', globals())
00169     img_group = ImageFile.ImageFile('www/GRUFGroups.gif', globals())
00170 
00171 
00172 
00173     #                                                                           #
00174     #                             OFFICIAL INTERFACE                            #
00175     #                                                                           #
00176 
00177     security.declarePublic("hasUsers")
00178     def hasUsers(self, ):
00179         """
00180         From Zope 2.7's User.py:
00181         This is not a formal API method: it is used only to provide
00182         a way for the quickstart page to determine if the default user
00183         folder contains any users to provide instructions on how to
00184         add a user for newbies.  Using getUserNames or getUsers would have
00185         posed a denial of service risk.
00186         In GRUF, this method always return 1."""
00187         return 1
00188 
00189     security.declareProtected(Permissions.manage_users, "user_names")
00190     def user_names(self,):
00191         """
00192         user_names() => return user IDS and not user NAMES !!!
00193         Due to a Zope inconsistency, the Role.get_valid_userids return user names
00194         and not user ids - which is bad. As GRUF distinguishes names and ids, this
00195         will cause it to break, especially in the listLocalRoles form. So we change
00196         user_names() behaviour so that it will return ids and not names.
00197         """
00198         return self.getUserIds()
00199 
00200 
00201     security.declareProtected(Permissions.manage_users, "getUserNames")
00202     def getUserNames(self, __include_groups__ = 1, __include_users__ = 1, __groups_prefixed__ = 0):
00203         """
00204         Return a list of all possible user atom names in the system.
00205         Groups will be returned WITHOUT their prefix by this method.
00206         So, there might be a collision between a user name and a group name.
00207         [NOTA: This method is time-expensive !]
00208         """
00209         if __include_users__:
00210             LogCallStack(LOG_DEBUG, "This call can be VERY expensive!")
00211         names = []
00212         ldap_sources = []
00213 
00214         # Fetch users in user sources
00215         if __include_users__:
00216             for src in self.listUserSources():
00217                 names.extend(src.getUserNames())
00218 
00219         # Append groups if possible
00220         if __include_groups__:
00221             # Regular groups
00222             if "acl_users" in self._getOb('Groups').objectIds():
00223                 names.extend(self.Groups.listGroups(prefixed = __groups_prefixed__))
00224 
00225             # LDAP groups
00226             for ldapuf in ldap_sources:
00227                 if ldapuf._local_groups:
00228                     continue
00229                 for g in ldapuf.getGroups(attr = LDAP_GROUP_RDN):
00230                     if __groups_prefixed__:
00231                         names.append("%s%s" % (GROUP_PREFIX, g))
00232                     else:
00233                         names.append(g)
00234         # Return a list of unique names
00235         return unique(names, _list = 1)
00236 
00237     security.declareProtected(Permissions.manage_users, "getUserIds")
00238     def getUserIds(self,):
00239         """
00240         Return a list of all possible user atom ids in the system.
00241         WARNING: Please see the id Vs. name consideration at the
00242         top of this document. So, groups will be returned
00243         WITH their prefix by this method
00244         [NOTA: This method is time-expensive !]
00245         """
00246         return self.getUserNames(__groups_prefixed__ = 1)
00247 
00248     security.declareProtected(Permissions.manage_users, "getUsers")
00249     def getUsers(self, __include_groups__ = 1, __include_users__ = 1):
00250         """Return a list of user and group objects.
00251         In case of some UF implementations, the returned object may only be a subset
00252         of all possible users.
00253         In other words, you CANNOT assert that len(getUsers()) equals len(getUserNames()).
00254         With cache-support UserFolders, such as LDAPUserFolder, the getUser() method will
00255         return only cached user objects instead of fetching all possible users.
00256         """
00257         Log(LOG_DEBUG, "getUsers")
00258         ret = []
00259         names_set = {}
00260 
00261         # avoid too many lookups for 'has_key' in loops
00262         isUserProcessed = names_set.has_key
00263 
00264         # Fetch groups first (then the user must be
00265         # prefixed by 'group_' prefix)
00266         if __include_groups__:
00267             # Fetch regular groups
00268             for u in self._getOb('Groups').acl_users.getUsers():
00269                 if not u:
00270                     continue        # Ignore empty users
00271 
00272                 name = u.getId()
00273                 if isUserProcessed(name):
00274                     continue        # Prevent double users inclusion
00275 
00276                 # Append group
00277                 names_set[name] = True
00278                 ret.append(
00279                     GRUFUser.GRUFGroup(u, self, isGroup = 1, source_id = "Groups").__of__(self)
00280                     )
00281 
00282         # Fetch users then
00283         if __include_users__:
00284             for src in self.listUserSources():
00285                 for u in src.getUsers():
00286                     if not u:
00287                         continue        # Ignore empty users
00288 
00289                     name = u.getId()
00290                     if isUserProcessed(name):
00291                         continue        # Prevent double users inclusion
00292 
00293                     # Append user
00294                     names_set[name] = True
00295                     ret.append(
00296                         GRUFUser.GRUFUser(u, self, source_id = src.getUserSourceId(), isGroup = 0).__of__(self)
00297                         )
00298 
00299         return tuple(ret)
00300 
00301     security.declareProtected(Permissions.manage_users, "getUser")
00302     def getUser(self, name, __include_users__ = 1, __include_groups__ = 1, __force_group_id__ = 0):
00303         """
00304         Return the named user object or None.
00305         User have precedence over group.
00306         If name is None, getUser() will return None.
00307         """
00308         # Basic check
00309         if name is None:
00310             return None
00311 
00312         # Prevent infinite recursion when instanciating a GRUF
00313         # without having sub-acl_users set
00314         if not "acl_users" in self._getOb('Groups').objectIds():
00315             return None
00316 
00317         # Fetch groups first (then the user must be prefixed by 'group_' prefix)
00318         if __include_groups__ and name.startswith(GROUP_PREFIX):
00319             id = name[GROUP_PREFIX_LEN:]
00320 
00321             # Fetch regular groups
00322             u = self._getOb('Groups')._getGroup(id)
00323             if u:
00324                 ret = GRUFUser.GRUFGroup(
00325                     u, self, isGroup = 1, source_id = "Groups"
00326                     ).__of__(self)
00327                 return ret              # XXX This violates precedence
00328 
00329         # Fetch users then
00330         if __include_users__:
00331             for src in self.listUserSources():
00332                 u = src.getUser(name)
00333                 if u:
00334                     ret = GRUFUser.GRUFUser(u, self, source_id = src.getUserSourceId(), isGroup = 0).__of__(self)
00335                     return ret
00336 
00337         # Then desperatly try to fetch groups (without beeing prefixed by 'group_' prefix)
00338         if __include_groups__ and (not __force_group_id__):
00339             u = self._getOb('Groups')._getGroup(name)
00340             if u:
00341                 ret = GRUFUser.GRUFGroup(u, self, isGroup = 1, source_id = "Groups").__of__(self)
00342                 return ret
00343 
00344         return None
00345 
00346 
00347     security.declareProtected(Permissions.manage_users, "getUserById")
00348     def getUserById(self, id, default=_marker):
00349         """Return the user atom corresponding to the given id. Can return groups.
00350         """
00351         ret = self.getUser(id, __force_group_id__ = 1)
00352         if not ret:
00353             if default is _marker:
00354                 return None
00355             ret = default
00356         return ret
00357 
00358 
00359     security.declareProtected(Permissions.manage_users, "getUserByName")
00360     def getUserByName(self, name, default=_marker):
00361         """Same as getUser() but works with a name instead of an id.
00362         [NOTA: Theorically, the id is a handle, while the name is the actual login name.
00363         But difference between a user id and a user name is unsignificant in
00364         all current User Folder implementations... except for GROUPS.]
00365         """
00366         # Try to fetch a user first
00367         usr = self.getUser(name)
00368 
00369         # If not found, try to fetch a group by appending the prefix
00370         if not usr:
00371             name = "%s%s" % (GROUP_PREFIX, name)
00372             usr = self.getUserById(name, default)
00373 
00374         return usr
00375 
00376     security.declareProtected(Permissions.manage_users, "getPureUserNames")
00377     def getPureUserNames(self, ):
00378         """Fetch the list of actual users from GRUFUsers.
00379         """
00380         return self.getUserNames(__include_groups__ = 0)
00381 
00382 
00383     security.declareProtected(Permissions.manage_users, "getPureUserIds")
00384     def getPureUserIds(self,):
00385         """Same as getUserIds() but without groups
00386         """
00387         return self.getUserNames(__include_groups__ = 0)
00388 
00389     security.declareProtected(Permissions.manage_users, "getPureUsers")
00390     def getPureUsers(self):
00391         """Return a list of pure user objects.
00392         """
00393         return self.getUsers(__include_groups__ = 0)
00394 
00395     security.declareProtected(Permissions.manage_users, "getPureUser")
00396     def getPureUser(self, id, ):
00397         """Return the named user object or None"""
00398         # Performance tricks
00399         if not id:
00400             return None
00401 
00402         # Fetch it
00403         return self.getUser(id, __include_groups__ = 0)
00404 
00405 
00406     security.declareProtected(Permissions.manage_users, "getGroupNames")
00407     def getGroupNames(self, ):
00408         """Same as getUserNames() but without pure users.
00409         """
00410         return self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 0)
00411 
00412     security.declareProtected(Permissions.manage_users, "getGroupIds")
00413     def getGroupIds(self, ):
00414         """Same as getUserNames() but without pure users.
00415         """
00416         return self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 1)
00417 
00418     security.declareProtected(Permissions.manage_users, "getGroups")
00419     def getGroups(self):
00420         """Same as getUsers() but without pure users.
00421         """
00422         return self.getUsers(__include_users__ = 0)
00423 
00424     security.declareProtected(Permissions.manage_users, "getGroup")
00425     def getGroup(self, name, prefixed = 1):
00426         """Return the named user object or None"""
00427         # Performance tricks
00428         if not name:
00429             return None
00430 
00431         # Unprefix group name
00432         if not name.startswith(GROUP_PREFIX):
00433             name = "%s%s" % (GROUP_PREFIX, name, )
00434 
00435         # Fetch it
00436         return self.getUser(name, __include_users__ = 0)
00437 
00438     security.declareProtected(Permissions.manage_users, "getGroupById")
00439     def getGroupById(self, id, default = _marker):
00440         """Same as getUserById(id) but forces returning a group.
00441         """
00442         ret = self.getUser(id, __include_users__ = 0, __force_group_id__ = 1)
00443         if not ret:
00444             if default is _marker:
00445                 return None
00446             ret = default
00447         return ret
00448 
00449     security.declareProtected(Permissions.manage_users, "getGroupByName")
00450     def getGroupByName(self, name, default = _marker):
00451         """Same as getUserByName(name) but forces returning a group.
00452         """
00453         ret = self.getUser(name, __include_users__ = 0, __force_group_id__ = 0)
00454         if not ret:
00455             if default is _marker:
00456                 return None
00457             ret = default
00458         return ret
00459 
00460 
00461 
00462     #                                                                           #
00463     #                              REGULAR MUTATORS                             #
00464     #                                                                           #
00465 
00466     security.declareProtected(Permissions.manage_users, "userFolderAddUser")
00467     def userFolderAddUser(self, name, password, roles, domains, groups = (),
00468                           REQUEST=None, **kw):
00469         """API method for creating a new user object. Note that not all
00470         user folder implementations support dynamic creation of user
00471         objects.
00472         """
00473         return self._doAddUser(name, password, roles, domains, groups, **kw)
00474     userFolderAddUser = postonly(userFolderAddUser)
00475 
00476     security.declareProtected(Permissions.manage_users, "userFolderEditUser")
00477     def userFolderEditUser(self, name, password, roles, domains, groups = None,
00478                            REQUEST=None, **kw):
00479         """API method for changing user object attributes. Note that not
00480         all user folder implementations support changing of user object
00481         attributes.
00482         Arguments ARE required.
00483         """
00484         return self._doChangeUser(name, password, roles, domains, groups, **kw)
00485     userFolderEditUser = postonly(userFolderEditUser)
00486 
00487     security.declareProtected(Permissions.manage_users, "userFolderUpdateUser")
00488     def userFolderUpdateUser(self, name, password = None, roles = None,
00489                              domains = None, groups = None, REQUEST=None, **kw):
00490         """API method for changing user object attributes. Note that not
00491         all user folder implementations support changing of user object
00492         attributes.
00493         Arguments are optional"""
00494         return self._updateUser(name, password, roles, domains, groups, **kw)
00495     userFolderUpdateUser = postonly(userFolderUpdateUser)
00496 
00497     security.declareProtected(Permissions.manage_users, "userFolderDelUsers")
00498     def userFolderDelUsers(self, names, REQUEST=None):
00499         """API method for deleting one or more user atom objects. Note that not
00500         all user folder implementations support deletion of user objects."""
00501         return self._doDelUsers(names)
00502     userFolderDelUsers = postonly(userFolderDelUsers)
00503 
00504     security.declareProtected(Permissions.manage_users, "userFolderAddGroup")
00505     def userFolderAddGroup(self, name, roles, groups = (), REQUEST=None, **kw):
00506         """API method for creating a new group.
00507         """
00508         while name.startswith(GROUP_PREFIX):
00509             name = name[GROUP_PREFIX_LEN:]
00510         return self._doAddGroup(name, roles, groups, **kw)
00511     userFolderAddGroup = postonly(userFolderAddGroup)
00512 
00513     security.declareProtected(Permissions.manage_users, "userFolderEditGroup")
00514     def userFolderEditGroup(self, name, roles, groups = None, REQUEST=None,
00515                             **kw):
00516         """API method for changing group object attributes.
00517         """
00518         return self._doChangeGroup(name, roles = roles, groups = groups, **kw)
00519     userFolderEditGroup = postonly(userFolderEditGroup)
00520 
00521     security.declareProtected(Permissions.manage_users, "userFolderUpdateGroup")
00522     def userFolderUpdateGroup(self, name, roles = None, groups = None,
00523                               REQUEST=None, **kw):
00524         """API method for changing group object attributes.
00525         """
00526         return self._updateGroup(name, roles = roles, groups = groups, **kw)
00527     userFolderUpdateGroup = postonly(userFolderUpdateGroup)
00528 
00529     security.declareProtected(Permissions.manage_users, "userFolderDelGroups")
00530     def userFolderDelGroups(self, names, REQUEST=None):
00531         """API method for deleting one or more group objects.
00532         Implem. note : All ids must be prefixed with 'group_',
00533         so this method ends up beeing only a filter of non-prefixed ids
00534         before calling userFolderDelUsers().
00535         """
00536         return self._doDelGroups(names)
00537     userFolderDelUsers = postonly(userFolderDelUsers)
00538 
00539 
00540 
00541     #                                                                           #
00542     #                               SEARCH METHODS                              #
00543     #                                                                           #
00544 
00545 
00546     security.declareProtected(Permissions.manage_users, "searchUsersByAttribute")
00547     def searchUsersByAttribute(self, attribute, search_term):
00548         """Return user ids whose 'attribute' match the specified search_term.
00549         If search_term is an empty string, behaviour depends on the underlying user folder:
00550         it may return all users, return only cached users (for LDAPUF) or return no users.
00551         This will return all users whose name contains search_term (whaterver its case).
00552         THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
00553         SEARCHING METHOD (ie. every UF kind except LDAPUF).
00554         'attribute' can be 'id' or 'name' for all UF kinds, or anything else for LDAPUF.
00555         """
00556         ret = []
00557         for src in self.listUserSources():
00558             # Use source-specific search methods if available
00559             if hasattr(src.aq_base, "findUser"):
00560                 # LDAPUF
00561                 Log(LOG_DEBUG, "We use LDAPUF to find users")
00562                 id_attr = src._uid_attr
00563                 if attribute == 'name':
00564                     attr = src._login_attr
00565                 elif attribute == 'id':
00566                     attr = src._uid_attr
00567                 else:
00568                     attr = attribute
00569                 Log(LOG_DEBUG, "we use findUser", attr, search_term, )
00570                 users = src.findUser(attr, search_term, exact_match = True)
00571                 ret.extend(
00572                     [ u[id_attr] for u in users ],
00573                     )
00574             else:
00575                 # Other types of user folder
00576                 search_term = search_term.lower()
00577 
00578                 # Find the proper method according to the attribute type
00579                 if attribute == "name":
00580                     method = "getName"
00581                 elif attribute == "id":
00582                     method = "getId"
00583                 else:
00584                     raise NotImplementedError, "Attribute searching is only supported for LDAPUserFolder by now."
00585 
00586                 # Actually search
00587                 src_id = src.getUserSourceId()
00588                 for u in src.getUsers():
00589                     if not u:
00590                         continue
00591                     u = GRUFUser.GRUFUser(u, self, source_id=src_id,
00592                                           isGroup=0).__of__(self)
00593                     s = getattr(u, method)().lower()
00594                     if string.find(s, search_term) != -1:
00595                         ret.append(u.getId())
00596         Log(LOG_DEBUG, "We've found them:", ret)
00597         return ret
00598 
00599     security.declareProtected(Permissions.manage_users, "searchUsersByName")
00600     def searchUsersByName(self, search_term):
00601         """Return user ids whose name match the specified search_term.
00602         If search_term is an empty string, behaviour depends on the underlying user folder:
00603         it may return all users, return only cached users (for LDAPUF) or return no users.
00604         This will return all users whose name contains search_term (whaterver its case).
00605         THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
00606         SEARCHING METHOD (ie. every UF kind except LDAPUF)
00607         """
00608         return self.searchUsersByAttribute("name", search_term)
00609 
00610     security.declareProtected(Permissions.manage_users, "searchUsersById")
00611     def searchUsersById(self, search_term):
00612         """Return user ids whose id match the specified search_term.
00613         If search_term is an empty string, behaviour depends on the underlying user folder:
00614         it may return all users, return only cached users (for LDAPUF) or return no users.
00615         This will return all users whose name contains search_term (whaterver its case).
00616         THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
00617         SEARCHING METHOD (ie. every UF kind except LDAPUF)
00618         """
00619         return self.searchUsersByAttribute("id", search_term)
00620 
00621 
00622     security.declareProtected(Permissions.manage_users, "searchGroupsByAttribute")
00623     def searchGroupsByAttribute(self, attribute, search_term):
00624         """Return group ids whose 'attribute' match the specified search_term.
00625         If search_term is an empty string, behaviour depends on the underlying group folder:
00626         it may return all groups, return only cached groups (for LDAPUF) or return no groups.
00627         This will return all groups whose name contains search_term (whaterver its case).
00628         THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
00629         SEARCHING METHOD (ie. every UF kind except LDAPUF).
00630         'attribute' can be 'id' or 'name' for all UF kinds, or anything else for LDAPUF.
00631         """
00632         ret = []
00633         src = self.Groups
00634 
00635         # Use source-specific search methods if available
00636         if hasattr(src.aq_base, "findGroup"):
00637             # LDAPUF
00638             id_attr = src._uid_attr
00639             if attribute == 'name':
00640                 attr = src._login_attr
00641             elif attribute == 'id':
00642                 attr = src._uid_attr
00643             else:
00644                 attr = attribute
00645             groups = src.findGroup(attr, search_term)
00646             ret.extend(
00647                 [ u[id_attr] for u in groups ],
00648                 )
00649         else:
00650             # Other types of group folder
00651             search_term = search_term.lower()
00652 
00653             # Find the proper method according to the attribute type
00654             if attribute == "name":
00655                 method = "getName"
00656             elif attribute == "id":
00657                 method = "getId"
00658             else:
00659                 raise NotImplementedError, "Attribute searching is only supported for LDAPGroupFolder by now."
00660 
00661             # Actually search
00662             for u in self.getGroups():
00663                 s = getattr(u, method)().lower()
00664                 if string.find(s, search_term) != -1:
00665                     ret.append(u.getId())
00666         return ret
00667 
00668     security.declareProtected(Permissions.manage_users, "searchGroupsByName")
00669     def searchGroupsByName(self, search_term):
00670         """Return group ids whose name match the specified search_term.
00671         If search_term is an empty string, behaviour depends on the underlying group folder:
00672         it may return all groups, return only cached groups (for LDAPUF) or return no groups.
00673         This will return all groups whose name contains search_term (whaterver its case).
00674         THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
00675         SEARCHING METHOD (ie. every UF kind except LDAPUF)
00676         """
00677         return self.searchGroupsByAttribute("name", search_term)
00678 
00679     security.declareProtected(Permissions.manage_users, "searchGroupsById")
00680     def searchGroupsById(self, search_term):
00681         """Return group ids whose id match the specified search_term.
00682         If search_term is an empty string, behaviour depends on the underlying group folder:
00683         it may return all groups, return only cached groups (for LDAPUF) or return no groups.
00684         This will return all groups whose name contains search_term (whaterver its case).
00685         THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
00686         SEARCHING METHOD (ie. every UF kind except LDAPUF)
00687         """
00688         return self.searchGroupsByAttribute("id", search_term)
00689 
00690     #                                                                           #
00691     #                         SECURITY MANAGEMENT METHODS                       #
00692     #                                                                           #
00693 
00694     security.declareProtected(Permissions.manage_users, "setRolesOnUsers")
00695     def setRolesOnUsers(self, roles, userids, REQUEST = None):
00696         """Set a common set of roles for a bunch of user atoms.
00697         """
00698         for usr in userids:
00699             self.userSetRoles(usr, roles)
00700     setRolesOnUsers = postonly(setRolesOnUsers)
00701 
00702 ##    def setUsersOfRole(self, usernames, role):
00703 ##        """Sets the users of a role.
00704 ##        XXX THIS METHOD SEEMS TO BE SEAMLESS.
00705 ##        """
00706 ##        raise NotImplementedError, "Not implemented."
00707 
00708     security.declareProtected(Permissions.manage_users, "getUsersOfRole")
00709     def getUsersOfRole(self, role, object = None):
00710         """Gets the user (and group) ids having the specified role...
00711         ...on the specified Zope object if it's not None
00712         ...on their own information if the object is None.
00713         NOTA: THIS METHOD IS VERY EXPENSIVE.
00714         XXX PERFORMANCES HAVE TO BE IMPROVED
00715         """
00716         ret = []
00717         for id in self.getUserIds():
00718             if role in self.getRolesOfUser(id):
00719                 ret.append(id)
00720         return tuple(ret)
00721 
00722     security.declarePublic("getRolesOfUser")
00723     def getRolesOfUser(self, userid):
00724         """Alias for user.getRoles()
00725         """
00726         return self.getUserById(userid).getRoles()
00727 
00728     security.declareProtected(Permissions.manage_users, "userFolderAddRole")
00729     def userFolderAddRole(self, role, REQUEST=None):
00730         """Add a new role. The role will be appended, in fact, in GRUF's surrounding folder.
00731         """
00732         if role in self.aq_parent.valid_roles():
00733             raise ValueError, "Role '%s' already exist" % (role, )
00734 
00735         return self.aq_parent._addRole(role)
00736     userFolderAddRole = postonly(userFolderAddRole)
00737 
00738     security.declareProtected(Permissions.manage_users, "userFolderDelRoles")
00739     def userFolderDelRoles(self, roles, REQUEST=None):
00740         """Delete roles.
00741         The removed roles will be removed from the UserFolder's users and groups as well,
00742         so this method can be very time consuming with a large number of users.
00743         """
00744         # Check that roles exist
00745         ud_roles = self.aq_parent.userdefined_roles()
00746         for r in roles:
00747             if not r in ud_roles:
00748                 raise ValueError, "Role '%s' is not defined on acl_users' parent folder" % (r, )
00749 
00750         # Remove role on all users
00751         for r in roles:
00752             for u in self.getUsersOfRole(r, ):
00753                 self.userRemoveRole(u, r, )
00754 
00755         # Actually remove role
00756         return self.aq_parent._delRoles(roles, None)
00757     userFolderDelRoles = postonly(userFolderDelRoles)
00758 
00759     security.declarePublic("userFolderGetRoles")
00760     def userFolderGetRoles(self, ):
00761         """
00762         userFolderGetRoles(self,) => tuple of strings
00763         List the roles defined at the top of GRUF's folder.
00764         This includes both user-defined roles and default roles.
00765         """
00766         return tuple(self.aq_parent.valid_roles())
00767 
00768 
00769     # Groups support
00770 
00771     security.declareProtected(Permissions.manage_users, "setMembers")
00772     def setMembers(self, groupid, userids, REQUEST=None):
00773         """Set the members of the group
00774         """
00775         self.getGroup(groupid).setMembers(userids)
00776     setMembers = postonly(setMembers)
00777 
00778     security.declareProtected(Permissions.manage_users, "addMember")
00779     def addMember(self, groupid, userid, REQUEST=None):
00780         """Add a member to a group
00781         """
00782         return self.getGroup(groupid).addMember(userid)
00783     addMember = postonly(addMember)
00784 
00785     security.declareProtected(Permissions.manage_users, "removeMember")
00786     def removeMember(self, groupid, userid, REQUEST=None):
00787         """Remove a member from a group.
00788         """
00789         return self.getGroup(groupid).removeMember(userid)
00790     removeMember = postonly(removeMember)
00791 
00792     security.declareProtected(Permissions.manage_users, "getMemberIds")
00793     def getMemberIds(self, groupid):
00794         """Return the list of member ids (groups and users) in this group
00795         """
00796         m = self.getGroup(groupid)
00797         if not m:
00798             raise ValueError, "Invalid group: '%s'" % groupid
00799         return self.getGroup(groupid).getMemberIds()
00800 
00801     security.declareProtected(Permissions.manage_users, "getUserMemberIds")
00802     def getUserMemberIds(self, groupid):
00803         """Return the list of member ids (groups and users) in this group
00804         """
00805         return self.getGroup(groupid).getUserMemberIds()
00806 
00807     security.declareProtected(Permissions.manage_users, "getGroupMemberIds")
00808     def getGroupMemberIds(self, groupid):
00809         """Return the list of member ids (groups and users) in this group
00810         XXX THIS MAY BE VERY EXPENSIVE !
00811         """
00812         return self.getGroup(groupid).getGroupMemberIds()
00813 
00814     security.declareProtected(Permissions.manage_users, "hasMember")
00815     def hasMember(self, groupid, id):
00816         """Return true if the specified atom id is in the group.
00817         This is the contrary of IUserAtom.isInGroup(groupid).
00818         THIS CAN BE VERY EXPENSIVE
00819         """
00820         return self.getGroup(groupid).hasMember(id)
00821 
00822 
00823     # User mutation
00824 
00825 ##    def setUserId(id, newId):
00826 ##        """Change id of a user atom.
00827 ##        """
00828 
00829 ##    def setUserName(id, newName):
00830 ##        """Change the name of a user atom.
00831 ##        """
00832 
00833     security.declareProtected(Permissions.manage_users, "userSetRoles")
00834     def userSetRoles(self, id, roles, REQUEST=None):
00835         """Change the roles of a user atom.
00836         """
00837         self._updateUser(id, roles = roles)
00838     userSetRoles = postonly(userSetRoles)
00839 
00840     security.declareProtected(Permissions.manage_users, "userAddRole")
00841     def userAddRole(self, id, role, REQUEST=None):
00842         """Append a role for a user atom
00843         """
00844         roles = list(self.getUser(id).getRoles())
00845         if not role in roles:
00846             roles.append(role)
00847             self._updateUser(id, roles = roles)
00848     userAddRole = postonly(userAddRole)
00849 
00850     security.declareProtected(Permissions.manage_users, "userRemoveRole")
00851     def userRemoveRole(self, id, role, REQUEST=None):
00852         """Remove the role of a user atom. Will NOT complain if role doesn't exist
00853         """
00854         roles = list(self.getRolesOfUser(id))
00855         if role in roles:
00856             roles.remove(role)
00857             self._updateUser(id, roles = roles)
00858     userRemoveRole = postonly(userRemoveRole)
00859 
00860     security.declareProtected(Permissions.manage_users, "userSetPassword")
00861     def userSetPassword(self, id, newPassword, REQUEST=None):
00862         """Set the password of a user
00863         """
00864         u = self.getPureUser(id)
00865         if not u:
00866             raise ValueError, "Invalid pure user id: '%s'" % (id,)
00867         self._updateUser(u.getId(), password = newPassword, )
00868     userSetPassword = postonly(userSetPassword)
00869 
00870     security.declareProtected(Permissions.manage_users, "userGetDomains")
00871     def userGetDomains(self, id):
00872         """get domains for a user
00873         """
00874         usr = self.getPureUser(id)
00875         return tuple(usr.getDomains())
00876 
00877     security.declareProtected(Permissions.manage_users, "userSetDomains")
00878     def userSetDomains(self, id, domains, REQUEST=None):
00879         """Set domains for a user
00880         """
00881         usr = self.getPureUser(id)
00882         self._updateUser(usr.getId(), domains = domains, )
00883     userSetDomains = postonly(userSetDomains)
00884 
00885     security.declareProtected(Permissions.manage_users, "userAddDomain")
00886     def userAddDomain(self, id, domain, REQUEST=None):
00887         """Append a domain to a user
00888         """
00889         usr = self.getPureUser(id)
00890         domains = list(usr.getDomains())
00891         if not domain in domains:
00892             roles.append(domain)
00893             self._updateUser(usr.getId(), domains = domains, )
00894     userAddDomain = postonly(userAddDomain)
00895 
00896     security.declareProtected(Permissions.manage_users, "userRemoveDomain")
00897     def userRemoveDomain(self, id, domain, REQUEST=None):
00898         """Remove a domain from a user
00899         """
00900         usr = self.getPureUser(id)
00901         domains = list(usr.getDomains())
00902         if not domain in domains:
00903             raise ValueError, "User '%s' doesn't have domain '%s'" % (id, domain, )
00904         while domain in domains:
00905             roles.remove(domain)
00906         self._updateUser(usr.getId(), domains = domains)
00907     userRemoveDomain = postonly(userRemoveDomain)
00908 
00909     security.declareProtected(Permissions.manage_users, "userSetGroups")
00910     def userSetGroups(self, id, groupnames, REQUEST=None):
00911         """Set the groups of a user
00912         """
00913         self._updateUser(id, groups = groupnames)
00914     userSetGroups = postonly(userSetGroups)
00915 
00916     security.declareProtected(Permissions.manage_users, "userAddGroup")
00917     def userAddGroup(self, id, groupname, REQUEST=None):
00918         """add a group to a user atom
00919         """
00920         groups = list(self.getUserById(id).getGroups())
00921         if not groupname in groups:
00922             groups.append(groupname)
00923             self._updateUser(id, groups = groups)
00924     userAddGroup = postonly(userAddGroup)
00925 
00926 
00927     security.declareProtected(Permissions.manage_users, "userRemoveGroup")
00928     def userRemoveGroup(self, id, groupname, REQUEST=None):
00929         """remove a group from a user atom.
00930         """
00931         groups = list(self.getUserById(id).getGroupNames())
00932         if groupname.startswith(GROUP_PREFIX):
00933             groupname = groupname[GROUP_PREFIX_LEN:]
00934         if groupname in groups:
00935             groups.remove(groupname)
00936             self._updateUser(id, groups = groups)
00937     userRemoveGroup = postonly(userRemoveGroup)
00938 
00939 
00940     #                                                                           #
00941     #                             VARIOUS OPERATIONS                            #
00942     #                                                                           #
00943 
00944     def __init__(self):
00945         """
00946         __init__(self) -> initialization method
00947         We define it to prevend calling ancestor's __init__ methods.
00948         """
00949         pass
00950 
00951 
00952     security.declarePrivate('_post_init')
00953     def _post_init(self):
00954         """
00955         _post_init(self) => meant to be called when the
00956                             object is in the Zope tree
00957         """
00958         uf = GRUFFolder.GRUFUsers()
00959         gf = GRUFFolder.GRUFGroups()
00960         self._setObject('Users', uf)
00961         self._setObject('Groups', gf)
00962         self.id = "acl_users"
00963 
00964     def manage_beforeDelete(self, item, container):
00965         """
00966         Special overloading for __allow_groups__ attribute
00967         """
00968         if item is self:
00969             try:
00970                 del container.__allow_groups__
00971             except:
00972                 pass
00973 
00974     def manage_afterAdd(self, item, container):
00975         """Same
00976         """
00977         if item is self:
00978             container.__allow_groups__ = aq_base(self)
00979 
00980     #                                                                   #
00981     #                           VARIOUS UTILITIES                       #
00982     #                                                                   #
00983     # These methods shouldn't be used directly for most applications,   #
00984     # but they might be useful for some special processing.             #
00985     #                                                                   #
00986 
00987     security.declarePublic('getGroupPrefix')
00988     def getGroupPrefix(self):
00989         """ group prefix """
00990         return GROUP_PREFIX
00991 
00992     security.declarePrivate('getGRUFPhysicalRoot')
00993     def getGRUFPhysicalRoot(self,):
00994         # $$$ trick meant to be used within
00995         # fake_getPhysicalRoot (see __init__)
00996         return self.getPhysicalRoot()
00997 
00998     security.declareProtected(Permissions.view, 'getGRUFId')
00999     def getGRUFId(self,):
01000         """
01001         Alias to self.getId()
01002         """
01003         return self.getId()
01004 
01005     security.declareProtected(Permissions.manage_users, "getUnwrappedUser")
01006     def getUnwrappedUser(self, name):
01007         """
01008         getUnwrappedUser(self, name) => user object or None
01009 
01010         This method is used to get a User object directly from the User's
01011         folder acl_users, without wrapping it with group information.
01012 
01013         This is useful for UserFolders that define additional User classes,
01014         when you want to call specific methods on these user objects.
01015 
01016         For example, LDAPUserFolder defines a 'getProperty' method that's
01017         not inherited from the standard User object. You can, then, use
01018         the getUnwrappedUser() to get the matching user and call this
01019         method.
01020         """
01021         src_id = self.getUser(name).getUserSourceId()
01022         return self.getUserSource(src_id).getUser(name)
01023 
01024     security.declareProtected(Permissions.manage_users, "getUnwrappedGroup")
01025     def getUnwrappedGroup(self, name):
01026         """
01027         getUnwrappedGroup(self, name) => user object or None
01028 
01029         Same as getUnwrappedUser but for groups.
01030         """
01031         return self.Groups.acl_users.getUser(name)
01032 
01033     #                                                                           #
01034     #                        AUTHENTICATION INTERFACE                           #
01035     #                                                                           #
01036 
01037     security.declarePrivate("authenticate")
01038     def authenticate(self, name, password, request):
01039         """
01040         Pass the request along to the underlying user-related UserFolder
01041         object
01042         THIS METHOD RETURNS A USER OBJECT OR NONE, as specified in the code
01043         in AccessControl/User.py.
01044         We also check for inituser in there.
01045         """
01046         # Emergency user checking stuff
01047         emergency = self._emergency_user
01048         if emergency and name == emergency.getUserName():
01049             if emergency.authenticate(password, request):
01050                 return emergency
01051             else:
01052                 return None
01053 
01054         # Usual GRUF authentication
01055         for src in self.listUserSources():
01056             # XXX We can imagine putting a try/except here to "ignore"
01057             # UF errors such as SQL or LDAP shutdown
01058             u = src.authenticate(name, password, request)
01059             if u:
01060                 return GRUFUser.GRUFUser(u, self, isGroup = 0, source_id = src.getUserSourceId()).__of__(self)
01061 
01062         # No acl_users in the Users folder or no user authenticated
01063         # => we refuse authentication
01064         return None
01065 
01066 
01067 
01068 
01069     #                                                                           #
01070     #                               GRUF'S GUTS :-)                             #
01071     #                                                                           #
01072 
01073     security.declarePrivate("_doAddUser")
01074     def _doAddUser(self, name, password, roles, domains, groups = (), **kw):
01075         """
01076         Create a new user. This should be implemented by subclasses to
01077         do the actual adding of a user. The 'password' will be the
01078         original input password, unencrypted. The implementation of this
01079         method is responsible for performing any needed encryption.
01080         """
01081         prefix = GROUP_PREFIX
01082 
01083         # Prepare groups
01084         roles = list(roles)
01085         gruf_groups = self.getGroupIds()
01086         for group in groups:
01087             if not group.startswith(prefix):
01088                 group = "%s%s" % (prefix, group, )
01089             if not group in gruf_groups:
01090                 raise ValueError, "Invalid group: '%s'" % (group, )
01091             roles.append(group)
01092 
01093         # Reset the users overview batch
01094         self._v_batch_users = []
01095 
01096         # Really add users
01097         return self.getDefaultUserSource()._doAddUser(
01098             name,
01099             password,
01100             roles,
01101             domains,
01102             **kw)
01103 
01104     security.declarePrivate("_doChangeUser")
01105     def _doChangeUser(self, name, password, roles, domains, groups = None, **kw):
01106         """
01107         Modify an existing user. This should be implemented by subclasses
01108         to make the actual changes to a user. The 'password' will be the
01109         original input password, unencrypted. The implementation of this
01110         method is responsible for performing any needed encryption.
01111 
01112         A None password should not change it (well, we hope so)
01113         """
01114         # Get actual user name and id
01115         usr = self.getUser(name)
01116         if usr is None:
01117             raise ValueError, "Invalid user: '%s'" % (name,)
01118         id = usr.getRealId()
01119 
01120         # Don't lose existing groups
01121         if groups is None:
01122             groups = usr.getGroups()
01123 
01124         roles = list(roles)
01125         groups = list(groups)
01126 
01127         # Change groups affectation
01128         cur_groups = self.getGroups()
01129         given_roles = tuple(usr.getRoles()) + tuple(roles)
01130         for group in groups:
01131             if not group.startswith(GROUP_PREFIX, ):
01132                 group = "%s%s" % (GROUP_PREFIX, group, )
01133             if not group in cur_groups and not group in given_roles:
01134                 roles.append(group)
01135 
01136         # Reset the users overview batch
01137         self._v_batch_users = []
01138 
01139         # Change the user itself
01140         src = usr.getUserSourceId()
01141         Log(LOG_NOTICE, name, "Source:", src)
01142         ret = self.getUserSource(src)._doChangeUser(
01143             id, password, roles, domains, **kw)
01144 
01145         # Invalidate user cache if necessary
01146         usr.clearCachedGroupsAndRoles()
01147         authenticated = getSecurityManager().getUser()
01148         if id == authenticated.getId() and hasattr(authenticated, 'clearCachedGroupsAndRoles'):
01149             authenticated.clearCachedGroupsAndRoles(self.getUserSource(src).getUser(id))
01150 
01151         return ret
01152 
01153     security.declarePrivate("_updateUser")
01154     def _updateUser(self, id, password = None, roles = None, domains = None, groups = None):
01155         """
01156         _updateUser(self, id, password = None, roles = None, domains = None, groups = None)
01157 
01158         This one should work for users AND groups.
01159 
01160         Front-end to _doChangeUser, but with a better default value support.
01161         We guarantee that None values will let the underlying UF keep the original ones.
01162         This is not true for the password: some buggy UF implementation may not
01163         handle None password correctly :-(
01164         """
01165         # Get the former values if necessary. Username must be valid !
01166         usr = self.getUser(id)
01167         if roles is None:
01168             # Remove invalid roles and group names
01169             roles = usr._original_roles
01170             roles = filter(lambda x: not x.startswith(GROUP_PREFIX), roles)
01171             roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared', ''), roles)
01172         else:
01173             # Check if roles are valid
01174             roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared', ''), roles)
01175             vr = self.userFolderGetRoles()
01176             for r in roles:
01177                 if not r in vr:
01178                     raise ValueError, "Invalid or inexistant role: '%s'." % (r, )
01179         if domains is None:
01180             domains = usr._original_domains
01181         if groups is None:
01182             groups = usr.getGroups(no_recurse = 1)
01183         else:
01184             # Check if given groups are valid
01185             glist = self.getGroupNames()
01186             glist.extend(map(lambda x: "%s%s" % (GROUP_PREFIX, x), glist))
01187             for g in groups:
01188                 if not g in glist:
01189                     raise ValueError, "Invalid group: '%s'" % (g, )
01190 
01191         # Reset the users overview batch
01192         self._v_batch_users = []
01193 
01194         # Change the user
01195         return self._doChangeUser(id, password, roles, domains, groups)
01196 
01197     security.declarePrivate("_doDelUsers")
01198     def _doDelUsers(self, names):
01199         """
01200         Delete one or more users. This should be implemented by subclasses
01201         to do the actual deleting of users.
01202         This won't delete groups !
01203         """
01204         # Collect information about user sources
01205         sources = {}
01206         for name in names:
01207             usr = self.getUser(name, __include_groups__ = 0)
01208             if not usr:
01209                 continue        # Ignore invalid user names
01210             src = usr.getUserSourceId()
01211             if not sources.has_key(src):
01212                 sources[src] = []
01213             sources[src].append(name)
01214         for src, names in sources.items():
01215             self.getUserSource(src)._doDelUsers(names)
01216 
01217         # Reset the users overview batch
01218         self._v_batch_users = []
01219 
01220 
01221     #                                   #
01222     #           Groups interface        #
01223     #                                   #
01224 
01225     security.declarePrivate("_doAddGroup")
01226     def _doAddGroup(self, name, roles, groups = (), **kw):
01227         """
01228         Create a new group. Password will be randomly created, and domain will be None.
01229         Supports nested groups.
01230         """
01231         # Prepare initial data
01232         domains = ()
01233         password = ""
01234         if roles is None:
01235             roles = []
01236         if groups is None:
01237             groups = []
01238 
01239         for x in range(0, 10):  # Password will be 10 chars long
01240             password = "%s%s" % (password, random.choice(string.lowercase), )
01241 
01242         # Compute roles
01243         roles = list(roles)
01244         prefix = GROUP_PREFIX
01245         gruf_groups = self.getGroupIds()
01246         for group in groups:
01247             if not group.startswith(prefix):
01248                 group = "%s%s" % (prefix, group, )
01249             if group == "%s%s" % (prefix, name, ):
01250                 raise ValueError, "Infinite recursion for group '%s'." % (group, )
01251             if not group in gruf_groups:
01252                 raise ValueError, "Invalid group: '%s' (defined groups are %s)" % (group, gruf_groups)
01253             roles.append(group)
01254 
01255         # Reset the users overview batch
01256         self._v_batch_users = []
01257 
01258         # Actual creation
01259         return self.Groups.acl_users._doAddUser(
01260             name, password, roles, domains, **kw
01261             )
01262 
01263     security.declarePrivate("_doChangeGroup")
01264     def _doChangeGroup(self, name, roles, groups = None, **kw):
01265         """Modify an existing group."""
01266         # Remove prefix if given
01267         if name.startswith(self.getGroupPrefix()):
01268             name = name[GROUP_PREFIX_LEN:]
01269 
01270         # Check if group exists
01271         grp = self.getGroup(name, prefixed = 0)
01272         if grp is None:
01273             raise ValueError, "Invalid group: '%s'" % (name,)
01274 
01275         # Don't lose existing groups
01276         if not groups:
01277             groups = grp.getGroups()
01278 
01279         roles = list(roles or [])
01280         groups = list(groups or [])
01281 
01282         # Change groups affectation
01283         cur_groups = self.getGroups()
01284         given_roles = tuple(grp.getRoles()) + tuple(roles)
01285         for group in groups:
01286             if not group.startswith(GROUP_PREFIX, ):
01287                 group = "%s%s" % (GROUP_PREFIX, group, )
01288             if group == "%s%s" % (GROUP_PREFIX, grp.id):
01289                 raise ValueError, "Cannot affect group '%s' to itself!" % (name, )        # Prevent direct inclusion of self
01290             new_grp = self.getGroup(group)
01291             if not new_grp:
01292                 raise ValueError, "Invalid or inexistant group: '%s'" % (group, )
01293             if "%s%s" % (GROUP_PREFIX, grp.id) in new_grp.getGroups():
01294                 raise ValueError, "Cannot affect %s to group '%s' as it would lead to circular references." % (group, name, )        # Prevent indirect inclusion of self
01295             if not group in cur_groups and not group in given_roles:
01296                 roles.append(group)
01297 
01298         # Reset the users overview batch
01299         self._v_batch_users = []
01300 
01301         # Perform the change
01302         domains = ""
01303         password = ""
01304         for x in range(0, 10):  # Password will be 10 chars long
01305             password = "%s%s" % (password, random.choice(string.lowercase), )
01306         return self.Groups.acl_users._doChangeUser(name, password,
01307                                                   roles, domains, **kw)
01308 
01309     security.declarePrivate("_updateGroup")
01310     def _updateGroup(self, name, roles = None, groups = None):
01311         """
01312         _updateGroup(self, name, roles = None, groups = None)
01313 
01314         Front-end to _doChangeUser, but with a better default value support.
01315         We guarantee that None values will let the underlying UF keep the original ones.
01316         This is not true for the password: some buggy UF implementation may not
01317         handle None password correctly but we do not care for Groups.
01318 
01319         group name can be prefixed or not
01320         """
01321         # Remove prefix if given
01322         if name.startswith(self.getGroupPrefix()):
01323             name = name[GROUP_PREFIX_LEN:]
01324 
01325         # Get the former values if necessary. Username must be valid !
01326         usr = self.getGroup(name, prefixed = 0)
01327         if roles is None:
01328             # Remove invalid roles and group names
01329             roles = usr._original_roles
01330             roles = filter(lambda x: not x.startswith(GROUP_PREFIX), roles)
01331             roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared'), roles)
01332         if groups is None:
01333             groups = usr.getGroups(no_recurse = 1)
01334 
01335         # Reset the users overview batch
01336         self._v_batch_users = []
01337 
01338         # Change the user
01339         return self._doChangeGroup(name, roles, groups)
01340 
01341 
01342     security.declarePrivate("_doDelGroup")
01343     def _doDelGroup(self, name):
01344         """Delete one user."""
01345         # Remove prefix if given
01346         if name.startswith(self.getGroupPrefix()):
01347             name = name[GROUP_PREFIX_LEN:]
01348 
01349         # Reset the users overview batch
01350         self._v_batch_users = []
01351 
01352         # Delete it
01353         return self.Groups.acl_users._doDelUsers([name])
01354 
01355     security.declarePrivate("_doDelGroups")
01356     def _doDelGroups(self, names):
01357         """Delete one or more users."""
01358         for group in names:
01359             if not self.getGroupByName(group, None):
01360                 continue        # Ignore invalid groups
01361             self._doDelGroup(group)
01362 
01363 
01364 
01365 
01366     #                                           #
01367     #      Pretty Management form methods       #
01368     #                                           #
01369 
01370 
01371     security.declarePublic('getGRUFVersion')
01372     def getGRUFVersion(self,):
01373         """
01374         getGRUFVersion(self,) => Return human-readable GRUF version as a string.
01375         """
01376         rev_date = "$Date: 2007-04-01 17:13:44 +0200 (dim, 01 avr 2007) $"[7:-2]
01377         return "%s / Revised %s" % (version__, rev_date)
01378 
01379 
01380     reset_entry = "__None__"            # Special entry used for reset
01381 
01382     security.declareProtected(Permissions.manage_users, "changeUser")
01383     def changeUser(self, user, groups = [], roles = [], REQUEST = {}, ):
01384         """
01385         changeUser(self, user, groups = [], roles = [], REQUEST = {}, ) => used in ZMI
01386         """
01387         obj = self.getUser(user)
01388         if obj.isGroup():
01389             self._updateGroup(name = user, groups = groups, roles = roles, )
01390         else:
01391             self._updateUser(id = user, groups = groups, roles = roles, )
01392 
01393 
01394         if REQUEST.has_key('RESPONSE'):
01395             return REQUEST.RESPONSE.redirect(self.absolute_url() + "/" + obj.getId() + "/manage_workspace?FORCE_USER=1")
01396     changeUser = postonly(changeUser)
01397 
01398     security.declareProtected(Permissions.manage_users, "deleteUser")
01399     def deleteUser(self, user, REQUEST = {}, ):
01400         """
01401         deleteUser(self, user, REQUEST = {}, ) => used in ZMI
01402         """
01403         pass
01404     deleteUser = postonly(deleteUser)
01405 
01406     security.declareProtected(Permissions.manage_users, "changeOrCreateUsers")
01407     def changeOrCreateUsers(self, users = [], groups = [], roles = [], new_users = [], default_password = '', REQUEST = {}, ):
01408         """
01409         changeOrCreateUsers => affect roles & groups to users and/or create new users
01410 
01411         All parameters are strings or lists (NOT tuples !).
01412         NO CHECKING IS DONE. This is an utility method, it's not part of the official API.
01413         """
01414         # Manage roles / groups deletion
01415         del_roles = 0
01416         del_groups = 0
01417         if self.reset_entry in roles:
01418             roles.remove(self.reset_entry)
01419             del_roles = 1
01420         if self.reset_entry in groups:
01421             groups.remove(self.reset_entry)
01422             del_groups = 1
01423         if not roles and not del_roles:
01424             roles = None                # None instead of [] to avoid deletion
01425             add_roles = []
01426         else:
01427             add_roles = roles
01428         if not groups and not del_groups:
01429             groups = None
01430             add_groups = []
01431         else:
01432             add_groups = groups
01433 
01434         # Passwords management
01435         passwords_list = []
01436 
01437         # Create brand new users
01438         for new in new_users:
01439             # Strip name
01440             name = string.strip(new)
01441             if not name:
01442                 continue
01443 
01444             # Avoid erasing former users
01445             if name in map(lambda x: x.getId(), self.getUsers()):
01446                 continue
01447 
01448             # Use default password or generate a random one
01449             if default_password:
01450                 password = default_password
01451             else:
01452                 password = ""
01453                 for x in range(0, 8):  # Password will be 8 chars long
01454                     password = "%s%s" % (password, random.choice("ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"), )
01455             self._doAddUser(name, password, add_roles, (), add_groups, )
01456 
01457             # Store the newly created password
01458             passwords_list.append({'name':name, 'password':password})
01459 
01460         # Update existing users
01461         for user in users:
01462             self._updateUser(id = user, groups = groups, roles = roles, )
01463 
01464         # Web request
01465         if REQUEST.has_key('RESPONSE'):
01466             # Redirect if no users have been created
01467             if not passwords_list:
01468                 return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_users")
01469 
01470             # Show passwords form
01471             else:
01472                 REQUEST.set('USER_PASSWORDS', passwords_list)
01473                 return self.manage_newusers(None, self)
01474 
01475         # Simply return the list of created passwords
01476         return passwords_list
01477     changeOrCreateUsers = postonly(changeOrCreateUsers)
01478 
01479     security.declareProtected(Permissions.manage_users, "deleteUsers")
01480     def deleteUsers(self, users = [], REQUEST = {}):
01481         """
01482         deleteUsers => explicit
01483 
01484         All parameters are strings. NO CHECKING IS DONE. This is an utility method !
01485         """
01486         # Delete them
01487         self._doDelUsers(users, )
01488 
01489         # Redirect
01490         if REQUEST.has_key('RESPONSE'):
01491             return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_users")
01492     deleteUsers = postonly(deleteUsers)
01493 
01494     security.declareProtected(Permissions.manage_users, "changeOrCreateGroups")
01495     def changeOrCreateGroups(self, groups = [], roles = [], nested_groups = [], new_groups = [], REQUEST = {}, ):
01496         """
01497         changeOrCreateGroups => affect roles to groups and/or create new groups
01498 
01499         All parameters are strings. NO CHECKING IS DONE. This is an utility method !
01500         """
01501         # Manage roles / groups deletion
01502         del_roles = 0
01503         del_groups = 0
01504         if self.reset_entry in roles:
01505             roles.remove(self.reset_entry)
01506             del_roles = 1
01507         if self.reset_entry in nested_groups:
01508             nested_groups.remove(self.reset_entry)
01509             del_groups = 1
01510         if not roles and not del_roles:
01511             roles = None                # None instead of [] to avoid deletion
01512             add_roles = []
01513         else:
01514             add_roles = roles
01515         if not nested_groups and not del_groups:
01516             nested_groups = None
01517             add_groups = []
01518         else:
01519             add_groups = nested_groups
01520 
01521         # Create brand new groups
01522         for new in new_groups:
01523             name = string.strip(new)
01524             if not name:
01525                 continue
01526             self._doAddGroup(name, roles, groups = add_groups)
01527 
01528         # Update existing groups
01529         for group in groups:
01530             self._updateGroup(group, roles = roles, groups = nested_groups)
01531 
01532         # Redirect
01533         if REQUEST.has_key('RESPONSE'):
01534             return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_groups")
01535     changeOrCreateGroups = postonly(changeOrCreateGroups)
01536 
01537     security.declareProtected(Permissions.manage_users, "deleteGroups")
01538     def deleteGroups(self, groups = [], REQUEST = {}):
01539         """
01540         deleteGroups => explicit
01541 
01542         All parameters are strings. NO CHECKING IS DONE. This is an utility method !
01543         """
01544         # Delete groups
01545         for group in groups:
01546             self._doDelGroup(group, )
01547 
01548         # Redirect
01549         if REQUEST.has_key('RESPONSE'):
01550             return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_groups")
01551     deleteGroups = postonly(deleteGroups)
01552 
01553     #                                                                   #
01554     #                   Local Roles Acquisition Blocking                #
01555     #   Those two methods perform their own security check.             #
01556     #                                                                   #
01557 
01558     security.declarePublic("acquireLocalRoles")
01559     def acquireLocalRoles(self, folder, status, REQUEST=None):
01560         """
01561         Enable or disable local role acquisition on the specified folder.
01562         If status is true, it will enable, else it will disable.
01563         Note that the user _must_ have the change_permissions permission on the
01564         folder to allow changes on it.
01565         If you want to use this code from a product, please use _acquireLocalRoles()
01566         instead: this private method won't check security on the destination folder.
01567         It's usually a bad idea to use _acquireLocalRoles() directly in your product,
01568         but, well, after all, you do what you want ! :^)
01569         """
01570         # Perform security check on destination folder
01571         if not getSecurityManager().checkPermission(Permissions.change_permissions, folder):
01572             raise Unauthorized(name = "acquireLocalRoles")
01573 
01574         return self._acquireLocalRoles(folder, status)
01575     acquireLocalRoles = postonly(acquireLocalRoles)
01576 
01577     def _acquireLocalRoles(self, folder, status):
01578         """Same as _acquireLocalRoles() but won't perform security check on the folder.
01579         """
01580         # Set the variable (or unset it if it's defined)
01581         if not status:
01582             folder.__ac_local_roles_block__ = 1
01583         else:
01584             if getattr(folder, '__ac_local_roles_block__', None):
01585                 folder.__ac_local_roles_block__ = None
01586 
01587 
01588     security.declarePublic("isLocalRoleAcquired")
01589     def isLocalRoleAcquired(self, folder):
01590         """Return true if the specified folder allows local role acquisition.
01591         """
01592         if getattr(folder, '__ac_local_roles_block__', None):
01593             return 0
01594         return 1
01595 
01596 
01597     #                                                                                   #
01598     #                           Security audit and info methods                         #
01599     #                                                                                   #
01600 
01601 
01602     # This method normally has NOT to be public ! It is because of a CMF inconsistancy.
01603     # folder_localrole_form is accessible to users who have the manage_properties permissions
01604     # (according to portal_types/Folder/Actions information). This is silly !
01605     # folder_localrole_form should be, in CMF, accessible only to those who have the
01606     # manage_users permissions instead of manage_properties permissions.
01607     # This is yet another one CMF bug we have to care about.
01608     # To deal with that in Plone2.1, we check for a particular permission on the destination
01609     # object _inside_ the method.
01610     security.declarePublic("getLocalRolesForDisplay")
01611     def getLocalRolesForDisplay(self, object):
01612         """This is used for plone's local roles display
01613         This method returns a tuple (massagedUsername, roles, userType, actualUserName).
01614         This method is protected by the 'Manage properties' permission. We may
01615         change that if it's too permissive..."""
01616         # Perform security check on destination object
01617         if not getSecurityManager().checkPermission(Permissions.manage_properties, object):
01618             raise Unauthorized(name = "getLocalRolesForDisplay")
01619 
01620         return self._getLocalRolesForDisplay(object)
01621 
01622     def _getLocalRolesForDisplay(self, object):
01623         """This is used for plone's local roles display
01624         This method returns a tuple (massagedUsername, roles, userType, actualUserName)"""
01625         result = []
01626         local_roles = object.get_local_roles()
01627         prefix = self.getGroupPrefix()
01628         for one_user in local_roles:
01629             massagedUsername = username = one_user[0]
01630             roles = one_user[1]
01631             userType = 'user'
01632             if prefix:
01633                 if self.getGroupById(username) is not None:
01634                     massagedUsername = username[len(prefix):]
01635                     userType = 'group'
01636             else:
01637                 userType = 'unknown'
01638             result.append((massagedUsername, roles, userType, username))
01639         return tuple(result)
01640 
01641 
01642     security.declarePublic("getAllLocalRoles")
01643     def getAllLocalRoles(self, object):
01644         """getAllLocalRoles(self, object): return a dictionnary {useratom_id: roles} of local
01645         roles defined AND herited at a certain point. This will handle lr-blocking
01646         as well.
01647         """
01648         # Perform security check on destination object
01649         if not getSecurityManager().checkPermission(Permissions.change_permissions, object):
01650             raise Unauthorized(name = "getAllLocalRoles")
01651 
01652         return self._getAllLocalRoles(object)
01653 
01654 
01655     def _getAllLocalRoles(self, object):
01656         """getAllLocalRoles(self, object): return a dictionnary {useratom_id: roles} of local
01657         roles defined AND herited at a certain point. This will handle lr-blocking
01658         as well.
01659         """
01660         # Modified from AccessControl.User.getRolesInContext().
01661         merged = {}
01662         object = getattr(object, 'aq_inner', object)
01663         while 1:
01664             if hasattr(object, '__ac_local_roles__'):
01665                 dict = object.__ac_local_roles__ or {}
01666                 if callable(dict): dict = dict()
01667                 for k, v in dict.items():
01668                     if not merged.has_key(k):
01669                         merged[k] = {}
01670                     for role in v:
01671                         merged[k][role] = 1
01672             if not self.isLocalRoleAcquired(object):
01673                 break
01674             if hasattr(object, 'aq_parent'):
01675                 object=object.aq_parent
01676                 object=getattr(object, 'aq_inner', object)
01677                 continue
01678             if hasattr(object, 'im_self'):
01679                 object=object.im_self
01680                 object=getattr(object, 'aq_inner', object)
01681                 continue
01682             break
01683         for key, value in merged.items():
01684             merged[key] = value.keys()
01685         return merged
01686 
01687 
01688 
01689     # Plone-specific security matrix computing method.
01690     security.declarePublic("getPloneSecurityMatrix")
01691     def getPloneSecurityMatrix(self, object):
01692         """getPloneSecurityMatrix(self, object): return a list of dicts of the current object
01693         and all its parents. The list is sorted with portal object first.
01694         Each dict has the following structure:
01695         {
01696           depth: (0 for portal root, 1 for 1st-level folders and so on),
01697           id:
01698           title:
01699           icon:
01700           absolute_url:
01701           security_permission: true if current user can change security on this object
01702           state: (workflow state)
01703           acquired_local_roles: 0 if local role blocking is enabled for this folder
01704           roles: {
01705             'role1': {
01706               'all_local_roles': [r1, r2, r3, ] (all defined local roles, including parent ones)
01707               'defined_local_roles': [r3, ] (local-defined only local roles)
01708               'permissions': ['Access contents information', 'Modify portal content', ] (only a subset)
01709               'same_permissions': true if same permissions as the parent
01710               'same_all_local_roles': true if all_local_roles is the same as the parent
01711               'same_defined_local_roles': true if defined_local_roles is the same as the parent
01712               },
01713             'role2': {...},
01714             },
01715         }
01716         """
01717         # Perform security check on destination object
01718         if not getSecurityManager().checkPermission(Permissions.access_contents_information, object):
01719             raise Unauthorized(name = "getPloneSecurityMatrix")
01720 
01721         # Basic inits
01722         mt = self.portal_membership
01723 
01724         # Fetch all possible roles in the portal
01725         all_roles = ['Anonymous'] + mt.getPortalRoles()
01726 
01727         # Fetch parent folders list until the portal
01728         all_objects = []
01729         cur_object = object
01730         while 1:
01731             if not getSecurityManager().checkPermission(Permissions.access_contents_information, cur_object):
01732                 raise Unauthorized(name = "getPloneSecurityMatrix")
01733             all_objects.append(cur_object)
01734             if cur_object.meta_type == "Plone Site":
01735                 break
01736             cur_object = object.aq_parent
01737         all_objects.reverse()
01738 
01739         # Scan those folders to get all the required information about them
01740         ret = []
01741         previous = None
01742         count = 0
01743         for obj in all_objects:
01744             # Basic information
01745             current = {
01746                 "depth": count,
01747                 "id": obj.getId(),
01748                 "title": obj.Title(),
01749                 "icon": obj.getIcon(),
01750                 "absolute_url": obj.absolute_url(),
01751                 "security_permission": getSecurityManager().checkPermission(Permissions.change_permissions, obj),
01752                 "acquired_local_roles": self.isLocalRoleAcquired(obj),
01753                 "roles": {},
01754                 "state": "XXX TODO XXX",         # XXX TODO
01755                 }
01756             count += 1
01757 
01758             # Workflow state
01759             # XXX TODO
01760 
01761             # Roles
01762             all_local_roles = {}
01763             local_roles = self._getAllLocalRoles(obj)
01764             for user, roles in self._getAllLocalRoles(obj).items():
01765                 for role in roles:
01766                     if not all_local_roles.has_key(role):
01767                         all_local_roles[role] = {}
01768                     all_local_roles[role][user] = 1
01769             defined_local_roles = {}
01770             if hasattr(obj.aq_base, 'get_local_roles'):
01771                 for user, roles in obj.get_local_roles():
01772                     for role in roles:
01773                         if not defined_local_roles.has_key(role):
01774                             defined_local_roles[role] = {}
01775                         defined_local_roles[role][user] = 1
01776 
01777             for role in all_roles:
01778                 all = all_local_roles.get(role, {}).keys()
01779                 defined = defined_local_roles.get(role, {}).keys()
01780                 all.sort()
01781                 defined.sort()
01782                 same_all_local_roles = 0
01783                 same_defined_local_roles = 0
01784                 if previous:
01785                     if previous['roles'][role]['all_local_roles'] == all:
01786                         same_all_local_roles = 1
01787                     if previous['roles'][role]['defined_local_roles'] == defined:
01788                         same_defined_local_roles = 1
01789 
01790                 current['roles'][role] = {
01791                     "all_local_roles": all,
01792                     "defined_local_roles": defined,
01793                     "same_all_local_roles": same_all_local_roles,
01794                     "same_defined_local_roles": same_defined_local_roles,
01795                     "permissions": [],  # XXX TODO
01796                     }
01797 
01798             ret.append(current)
01799             previous = current
01800 
01801         return ret
01802 
01803 
01804     security.declareProtected(Permissions.manage_users, "computeSecuritySettings")
01805     def computeSecuritySettings(self, folders, actors, permissions, cache = {}):
01806         """
01807         computeSecuritySettings(self, folders, actors, permissions, cache = {}) => return a structure that is suitable for security audit Page Template.
01808 
01809         - folders is the structure returned by getSiteTree()
01810         - actors is the structure returned by listUsersAndRoles()
01811         - permissions is ((id: permission), (id: permission), ...)
01812         - cache is passed along requests to make computing faster
01813         """
01814         # Scan folders and actors to get the relevant information
01815         usr_cache = {}
01816         for id, depth, path in folders:
01817             folder = self.unrestrictedTraverse(path)
01818             for kind, actor, display, handle, html in actors:
01819                 if kind in ("user", "group"):
01820                     # Init structure
01821                     if not cache.has_key(path):
01822                         cache[path] = {(kind, actor): {}}
01823                     elif not cache[path].has_key((kind, actor)):
01824                         cache[path][(kind, actor)] = {}
01825                     else:
01826                         cache[path][(kind, actor)] = {}
01827 
01828                     # Split kind into groups and get individual role information
01829                     perm_keys = []
01830                     usr = usr_cache.get(actor)
01831                     if not usr:
01832                         usr = self.getUser(actor)
01833                         usr_cache[actor] = usr
01834                     roles = usr.getRolesInContext(folder,)
01835                     for role in roles:
01836                         for perm_key in self.computeSetting(path, folder, role, permissions, cache).keys():
01837                             cache[path][(kind, actor)][perm_key] = 1
01838 
01839                 else:
01840                     # Get role information
01841                     self.computeSetting(path, folder, actor, permissions, cache)
01842 
01843         # Return the computed cache
01844         return cache
01845 
01846 
01847     security.declareProtected(Permissions.manage_users, "computeSetting")
01848     def computeSetting(self, path, folder, actor, permissions, cache):
01849         """
01850         computeSetting(......) => used by computeSecuritySettings to populate the cache for ROLES
01851         """
01852         # Avoid doing things twice
01853         kind = "role"
01854         if cache.get(path, {}).get((kind, actor), None) is not None:
01855             return cache[path][(kind, actor)]
01856 
01857         # Initilize cache structure
01858         if not cache.has_key(path):
01859             cache[path] = {(kind, actor): {}}
01860         elif not cache[path].has_key((kind, actor)):
01861             cache[path][(kind, actor)] = {}
01862 
01863         # Analyze permission settings
01864         ps = folder.permission_settings()
01865         for perm_key, permission in permissions:
01866             # Check acquisition of permission setting.
01867             can = 0
01868             acquired = 0
01869             for p in ps:
01870                 if p['name'] == permission:
01871                     acquired = not not p['acquire']
01872 
01873             # If acquired, call the parent recursively
01874             if acquired:
01875                 parent = folder.aq_parent.getPhysicalPath()
01876                 perms = self.computeSetting(parent, self.unrestrictedTraverse(parent), actor, permissions, cache)
01877                 can = perms.get(perm_key, None)
01878 
01879             # Else, check permission here
01880             else:
01881                 for p in folder.rolesOfPermission(permission):
01882                     if p['name'] == "Anonymous":
01883                         # If anonymous is allowed, then everyone is allowed
01884                         if p['selected']:
01885                             can = 1
01886                             break
01887                     if p['name'] == actor:
01888                         if p['selected']:
01889                             can = 1
01890                             break
01891 
01892             # Extend the data structure according to 'can' setting
01893             if can:
01894                 cache[path][(kind, actor)][perm_key] = 1
01895 
01896         return cache[path][(kind, actor)]
01897 
01898 
01899     security.declarePrivate('_getNextHandle')
01900     def _getNextHandle(self, index):
01901         """
01902         _getNextHandle(self, index) => utility function to
01903         get an unique handle for each legend item.
01904         """
01905         return "%02d" % index
01906 
01907 
01908     security.declareProtected(Permissions.manage_users, "listUsersAndRoles")
01909     def listUsersAndRoles(self,):
01910         """
01911         listUsersAndRoles(self,) => list of tuples
01912 
01913         This method is used by the Security Audit page.
01914         XXX HAS TO BE OPTIMIZED
01915         """
01916         request = self.REQUEST
01917         display_roles = request.get('display_roles', 0)
01918         display_groups = request.get('display_groups', 0)
01919         display_users = request.get('display_users', 0)
01920 
01921         role_index = 0
01922         user_index = 0
01923         group_index = 0
01924         ret = []
01925 
01926         # Collect roles
01927         if display_roles:
01928             for r in self.aq_parent.valid_roles():
01929                 handle = "R%02d" % role_index
01930                 role_index += 1
01931                 ret.append(('role', r, r, handle, r))
01932 
01933         # Collect users
01934         if display_users:
01935             for u in map(lambda x: x.getId(), self.getPureUsers()):
01936                 obj = self.getUser(u)
01937                 html = obj.asHTML()
01938                 handle = "U%02d" % user_index
01939                 user_index += 1
01940                 ret.append(('user', u, u, handle, html))
01941 
01942         if display_groups:
01943             for u in self.getGroupNames():
01944                 obj = self.getUser(u)
01945                 handle = "G%02d" % group_index
01946                 html = obj.asHTML()
01947                 group_index += 1
01948                 ret.append(('group', u, obj.getUserNameWithoutGroupPrefix(), handle, html))
01949 
01950         # Return list
01951         return ret
01952 
01953     security.declareProtected(Permissions.manage_users, "getSiteTree")
01954     def getSiteTree(self, obj=None, depth=0):
01955         """
01956         getSiteTree(self, obj=None, depth=0) => special structure
01957 
01958         This is used by the security audit page
01959         """
01960         ret = []
01961         if not obj:
01962             if depth==0:
01963                 obj = self.aq_parent
01964             else:
01965                 return ret
01966 
01967         ret.append([obj.getId(), depth, string.join(obj.getPhysicalPath(), '/')])
01968         for sub in obj.objectValues():
01969             try:
01970                 # Ignore user folders
01971                 if sub.getId() in ('acl_users', ):
01972                     continue
01973 
01974                 # Ignore portal_* stuff
01975                 if sub.getId()[:len('portal_')] == 'portal_':
01976                     continue
01977 
01978                 if sub.isPrincipiaFolderish:
01979                     ret.extend(self.getSiteTree(sub, depth + 1))
01980 
01981             except:
01982                 # We ignore exceptions
01983                 pass
01984 
01985         return ret
01986 
01987     security.declareProtected(Permissions.manage_users, "listAuditPermissions")
01988     def listAuditPermissions(self,):
01989         """
01990         listAuditPermissions(self,) => return a list of eligible permissions
01991         """
01992         ps = self.permission_settings()
01993         return map(lambda p: p['name'], ps)
01994 
01995     security.declareProtected(Permissions.manage_users, "getDefaultPermissions")
01996     def getDefaultPermissions(self,):
01997         """
01998         getDefaultPermissions(self,) => return default R & W permissions for security audit.
01999         """
02000         # If there's a Plone site in the above folder, use plonish permissions
02001         hasPlone = 0
02002         p = self.aq_parent
02003         if p.meta_type == "CMF Site":
02004             hasPlone = 1
02005         else:
02006             for obj in p.objectValues():
02007                 if obj.meta_type == "CMF Site":
02008                     hasPlone = 1
02009                     break
02010 
02011         if hasPlone:
02012             return {'R': 'View',
02013                     'W': 'Modify portal content',
02014                     }
02015         else:
02016             return {'R': 'View',
02017                     'W': 'Change Images and Files',
02018                     }
02019 
02020 
02021     #                                                                           #
02022     #                           Users/Groups tree view                          #
02023     #                                (ZMI only)                                 #
02024     #                                                                           #
02025 
02026 
02027     security.declarePrivate('getTreeInfo')
02028     def getTreeInfo(self, usr, dict = {}):
02029         "utility method"
02030         # Prevend infinite recursions
02031         name = usr.getUserName()
02032         if dict.has_key(name):
02033             return
02034         dict[name] = {}
02035 
02036         # Properties
02037         noprefix = usr.getUserNameWithoutGroupPrefix()
02038         is_group = usr.isGroup()
02039         if usr.isGroup():
02040             icon = string.join(self.getPhysicalPath(), '/') + '/img_group'
02041 ##            icon = self.absolute_url() + '/img_group'
02042         else:
02043             icon = ' img_user'
02044 ##            icon = self.absolute_url() + '/img_user'
02045 
02046         # Subobjects
02047         belongs_to = []
02048         for grp in usr.getGroups(no_recurse = 1):
02049             belongs_to.append(grp)
02050             self.getTreeInfo(self.getGroup(grp))
02051 
02052         # Append (and return) structure
02053         dict[name] = {
02054             "name": noprefix,
02055             "is_group": is_group,
02056             "icon": icon,
02057             "belongs_to": belongs_to,
02058             }
02059         return dict
02060 
02061 
02062     security.declarePrivate("tpValues")
02063     def tpValues(self):
02064         # Avoid returning HUUUUUUGE lists
02065         # Use the cache at first
02066         if self._v_no_tree and self._v_cache_no_tree > time.time():
02067             return []        # Do not use the tree
02068 
02069         # XXX - I DISABLE THE TREE BY NOW (Pb. with icon URL)
02070         return []
02071 
02072         # Then, use a simple computation to determine opportunity to use the tree or not
02073         ngroups = len(self.getGroupNames())
02074         if ngroups > MAX_TREE_USERS_AND_GROUPS:
02075             self._v_no_tree = 1
02076             self._v_cache_no_tree = time.time() + TREE_CACHE_TIME
02077             return []
02078         nusers = len(self.getUsers())
02079         if ngroups + nusers > MAX_TREE_USERS_AND_GROUPS:
02080             meth_list = self.getGroups
02081         else:
02082             meth_list = self.getUsers
02083         self._v_no_tree = 0
02084 
02085         # Get top-level user and groups list
02086         tree_dict = {}
02087         top_level_names = []
02088         top_level = []
02089         for usr in meth_list():
02090             self.getTreeInfo(usr, tree_dict)
02091             if not usr.getGroups(no_recurse = 1):
02092                 top_level_names.append(usr.getUserName())
02093         for id in top_level_names:
02094             top_level.append(treeWrapper(id, tree_dict))
02095 
02096         # Return this top-level list
02097         top_level.sort(lambda x, y: cmp(x.sortId(), y.sortId()))
02098         return top_level
02099 
02100 
02101     def tpId(self,):
02102         return self.getId()
02103 
02104 
02105     #                                                                           #
02106     #                      Direct traversal to user or group info               #
02107     #                                                                           #
02108 
02109     def manage_workspace(self, REQUEST):
02110         """
02111         manage_workspace(self, REQUEST) => Overrided to allow direct user or group traversal
02112         via the left tree view.
02113         """
02114         path = string.split(REQUEST.PATH_INFO, '/')[:-1]
02115         userid = path[-1]
02116 
02117         # Use individual usr/grp management screen (only if name is passed along the mgt URL)
02118         if userid != "acl_users":
02119             usr = self.getUserById(userid)
02120             if usr:
02121                 REQUEST.set('username', userid)
02122                 REQUEST.set('MANAGE_TABS_NO_BANNER', '1')   # Prevent use of the manage banner
02123                 return self.restrictedTraverse('manage_user')()
02124 
02125         # Default management screen
02126         return self.restrictedTraverse('manage_overview')()
02127 
02128 
02129     # Tree caching information
02130     _v_no_tree =  0
02131     _v_cache_no_tree = 0
02132     _v_cache_tree = (0, [])
02133 
02134 
02135     def __bobo_traverse__(self, request, name):
02136         """
02137         Looks for the name of a user or a group.
02138         This applies only if users list is not huge.
02139         """
02140         # Check if it's an attribute
02141         if hasattr(self.aq_base, name, ):
02142             return getattr(self, name)
02143 
02144         # It's not an attribute, maybe it's a user/group
02145         # (this feature is used for the tree)
02146         if name.startswith('_'):
02147             pass        # Do not fetch users
02148         elif name.startswith('manage_'):
02149             pass        # Do not fetch users
02150         elif name in INVALID_USER_NAMES:
02151             pass        # Do not fetch users
02152         else:
02153             # Only try to get users is fetch_user is true.
02154             # This is only for performance reasons.
02155             # The following code block represent what we want to minimize
02156             if self._v_cache_tree[0] < time.time():
02157                 un = map(lambda x: x.getId(), self.getUsers())            # This is the cost we want to avoid
02158                 self._v_cache_tree = (time.time() + TREE_CACHE_TIME, un, )
02159             else:
02160                 un = self._v_cache_tree[1]
02161 
02162             # Get the user if we can
02163             if name in un:
02164                 self._v_no_tree = 0
02165                 return self
02166 
02167             # Force getting the user if we must
02168             if request.get("FORCE_USER"):
02169                 self._v_no_tree = 0
02170                 return self
02171 
02172         # This will raise if it's not possible to acquire 'name'
02173         return getattr(self, name, )
02174 
02175 
02176 
02177     #                                                                                   #
02178     #                           USERS / GROUPS BATCHING (ZMI SCREENS)                   #
02179     #                                                                                   #
02180 
02181     _v_batch_users = []
02182 
02183     security.declareProtected(Permissions.view_management_screens, "listUsersBatches")
02184     def listUsersBatches(self,):
02185         """
02186         listUsersBatches(self,) => return a list of (start, end) tuples.
02187         Return None if batching is not necessary
02188         """
02189         # Time-consuming stuff !
02190         un = map(lambda x: x.getId(), self.getPureUsers())
02191         if len(un) <= MAX_USERS_PER_PAGE:
02192             return None
02193         un.sort()
02194 
02195         # Split this list into small groups if necessary
02196         ret = []
02197         idx = 0
02198         l_un = len(un)
02199         nbatches = int(math.ceil(l_un / float(MAX_USERS_PER_PAGE)))
02200         for idx in range(0, nbatches):
02201             first = idx * MAX_USERS_PER_PAGE
02202             last = first + MAX_USERS_PER_PAGE - 1
02203             if last >= l_un:
02204                 last = l_un - 1
02205             # Append a tuple (not dict) to avoid too much memory consumption
02206             ret.append((first, last, un[first], un[last]))
02207 
02208         # Cache & return it
02209         self._v_batch_users = un
02210         return ret
02211 
02212     security.declareProtected(Permissions.view_management_screens, "listUsersBatchTable")
02213     def listUsersBatchTable(self,):
02214         """
02215         listUsersBatchTable(self,) => Same a mgt screens but divided into sublists to
02216         present them into 5 columns.
02217         XXX have to merge this w/getUsersBatch to make it in one single pass
02218         """
02219         # Iterate
02220         ret = []
02221         idx = 0
02222         current = []
02223         for rec in (self.listUsersBatches() or []):
02224             if not idx % 5:
02225                 if current:
02226                     ret.append(current)
02227                 current = []
02228             current.append(rec)
02229             idx += 1
02230 
02231         if current:
02232             ret.append(current)
02233 
02234         return ret
02235 
02236     security.declareProtected(Permissions.view_management_screens, "getUsersBatch")
02237     def getUsersBatch(self, start):
02238         """
02239         getUsersBatch(self, start) => user list
02240         """
02241         # Rebuild the list if necessary
02242         if not self._v_batch_users:
02243             un = map(lambda x: x.getId(), self.getPureUsers())
02244             self._v_batch_users = un
02245 
02246         # Return the batch
02247         end = start + MAX_USERS_PER_PAGE
02248         ids = self._v_batch_users[start:end]
02249         ret = []
02250         for id in ids:
02251             usr = self.getUser(id)
02252             if usr:                     # Prevent adding invalid users
02253                 ret.append(usr)
02254         return ret
02255 
02256 
02257     #                                                                            #
02258     #                         Multiple sources management                        #
02259     #                                                                            #
02260 
02261     # Arrows
02262     img_up_arrow = ImageFile.ImageFile('www/up_arrow.gif', globals())
02263     img_down_arrow = ImageFile.ImageFile('www/down_arrow.gif', globals())
02264     img_up_arrow_grey = ImageFile.ImageFile('www/up_arrow_grey.gif', globals())
02265     img_down_arrow_grey = ImageFile.ImageFile('www/down_arrow_grey.gif', globals())
02266 
02267     security.declareProtected(Permissions.manage_users, "toggleSource")
02268     def toggleSource(self, src_id, REQUEST = {}):
02269         """
02270         toggleSource(self, src_id, REQUEST = {}) => toggle enabled/disabled source
02271         """
02272         # Find the source
02273         ids = self.objectIds('GRUFUsers')
02274         if not src_id in ids:
02275             raise ValueError, "Invalid source: '%s' (%s)" % (src_id, ids)
02276         src = getattr(self, src_id)
02277         if src.enabled:
02278             src.disableSource()
02279         else:
02280             src.enableSource()
02281 
02282         # Redirect where we want to
02283         if REQUEST.has_key('RESPONSE'):
02284             return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
02285 
02286 
02287     security.declareProtected(Permissions.manage_users, "listUserSources")
02288     def listUserSources(self, ):
02289         """
02290         listUserSources(self, ) => Return a list of userfolder objects
02291         Only return VALID (ie containing an acl_users) user sources if all is None
02292         XXX HAS TO BE OPTIMIZED VERY MUCH!
02293         We add a check in debug mode to ensure that invalid sources won't be added
02294         to the list.
02295         This method return only _enabled_ user sources.
02296         """
02297         ret = []
02298         dret = {}
02299         if DEBUG_MODE:
02300             for src in self.objectValues(['GRUFUsers']):
02301                 if not src.enabled:
02302                     continue
02303                 if 'acl_users' in src.objectIds():
02304                     if getattr(aq_base(src.acl_users), 'authenticate', None):   # Additional check in debug mode
02305                         dret[src.id] = src.acl_users                            # we cannot use restrictedTraverse here because
02306                                                                                 # of infinite recursion issues.
02307         else:
02308             for src in self.objectValues(['GRUFUsers']):
02309                 if not src.enabled:
02310                     continue
02311                 if not 'acl_users' in src.objectIds():
02312                     continue
02313                 dret[src.id] = src.acl_users
02314         ret = dret.items()
02315         ret.sort()
02316         return [ src[1] for src in ret ]
02317 
02318     security.declareProtected(Permissions.manage_users, "listUserSourceFolders")
02319     def listUserSourceFolders(self, ):
02320         """
02321         listUserSources(self, ) => Return a list of GRUFUsers objects
02322         """
02323         ret = []
02324         for src in self.objectValues(['GRUFUsers']):
02325             ret.append(src)
02326         ret.sort(lambda x,y: cmp(x.id, y.id))
02327         return ret
02328 
02329     security.declarePrivate("getUserSource")
02330     def getUserSource(self, id):
02331         """
02332         getUserSource(self, id) => GRUFUsers.acl_users object.
02333         Raises if no acl_users available
02334         """
02335         return getattr(self, id).acl_users
02336 
02337     security.declarePrivate("getUserSourceFolder")
02338     def getUserSourceFolder(self, id):
02339         """
02340         getUserSourceFolder(self, id) => GRUFUsers object
02341         """
02342         return getattr(self, id)
02343 
02344     security.declareProtected(Permissions.manage_users, "addUserSource")
02345     def addUserSource(self, factory_uri, REQUEST = {}, *args, **kw):
02346         """
02347         addUserSource(self, factory_uri, REQUEST = {}, *args, **kw) => redirect
02348         Adds the specified user folder
02349         """
02350         # Get the initial Users id
02351         ids = self.objectIds('GRUFUsers')
02352         if ids:
02353             ids.sort()
02354             if ids == ['Users',]:
02355                 last = 0
02356             else:
02357                 last = int(ids[-1][-2:])
02358             next_id = "Users%02d" % (last + 1, )
02359         else:
02360             next_id = "Users"
02361 
02362         # Add the GRUFFolder object
02363         uf = GRUFFolder.GRUFUsers(id = next_id)
02364         self._setObject(next_id, uf)
02365 
02366 ##        # If we use ldap, tag it
02367 ##        if string.find(factory_uri.lower(), "ldap") > -1:
02368 ##            self._haveLDAPUF += 1
02369 
02370         # Add its underlying UserFolder
02371         # If we're called TTW, uses a redirect else tries to call the UF factory directly
02372         if REQUEST.has_key('RESPONSE'):
02373             return REQUEST.RESPONSE.redirect("%s/%s/%s" % (self.absolute_url(), next_id, factory_uri))
02374         return getattr(self, next_id).unrestrictedTraverse(factory_uri)(*args, **kw)
02375     addUserSource = postonly(addUserSource)
02376 
02377     security.declareProtected(Permissions.manage_users, "deleteUserSource")
02378     def deleteUserSource(self, id = None, REQUEST = {}):
02379         """
02380         deleteUserSource(self, id = None, REQUEST = {}) => Delete the specified user source
02381         """
02382         # Check the source id
02383         if type(id) != type('s'):
02384             raise ValueError, "You must choose a valid source to delete and confirm it."
02385 
02386         # Delete it
02387         self.manage_delObjects([id,])
02388         if REQUEST.has_key('RESPONSE'):
02389             return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
02390     deleteUserSource = postonly(deleteUserSource)
02391 
02392     security.declareProtected(Permissions.manage_users, "getDefaultUserSource")
02393     def getDefaultUserSource(self,):
02394         """
02395         getDefaultUserSource(self,) => acl_users object
02396         Return default user source for user writing.
02397         XXX By now, the FIRST source is the default one. This may change in the future.
02398         """
02399         lst = self.listUserSources()
02400         if not lst:
02401             raise RuntimeError, "No valid User Source to add users in."
02402         return lst[0]
02403 
02404 
02405     security.declareProtected(Permissions.manage_users, "listAvailableUserSources")
02406     def listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1):
02407         """
02408         listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1) => tuples (name, factory_uri)
02409         List UserFolder replacement candidates.
02410 
02411         - if filter_classes is true, return only ones which have a base UserFolder class
02412         - if filter_permissions, return only types the user has rights to add
02413         """
02414         ret = []
02415 
02416         # Fetch candidate types
02417         user = getSecurityManager().getUser()
02418         meta_types = []
02419         if callable(self.all_meta_types):
02420             all=self.all_meta_types()
02421         else:
02422             all=self.all_meta_types
02423         for meta_type in all:
02424             if filter_permissions and meta_type.has_key('permission'):
02425                 if user.has_permission(meta_type['permission'],self):
02426                     meta_types.append(meta_type)
02427             else:
02428                 meta_types.append(meta_type)
02429 
02430         # Keep only, if needed, BasicUserFolder-derived classes
02431         for t in meta_types:
02432             if t['name'] == self.meta_type:
02433                 continue        # Do not keep GRUF ! ;-)
02434 
02435             if filter_classes:
02436                 try:
02437                     if t.get('instance', None) and t['instance'].isAUserFolder:
02438                         ret.append((t['name'], t['action']))
02439                         continue
02440                     if t.get('instance', None) and class_utility.isBaseClass(AccessControl.User.BasicUserFolder, t['instance']):
02441                         ret.append((t['name'], t['action']))
02442                         continue
02443                 except AttributeError:
02444                     pass        # We ignore 'invalid' instances (ie. that wouldn't define a __base__ attribute)
02445             else:
02446                 ret.append((t['name'], t['action']))
02447 
02448         return tuple(ret)
02449 
02450     security.declareProtected(Permissions.manage_users, "moveUserSourceUp")
02451     def moveUserSourceUp(self, id, REQUEST = {}):
02452         """
02453         moveUserSourceUp(self, id, REQUEST = {}) => used in management screens
02454         try to get ids as consistant as possible
02455         """
02456         # List and sort sources and preliminary checks
02457         ids = self.objectIds('GRUFUsers')
02458         ids.sort()
02459         if not ids or not id in ids:
02460             raise ValueError, "Invalid User Source: '%s'" % (id,)
02461 
02462         # Find indexes to swap
02463         src_index = ids.index(id)
02464         if src_index == 0:
02465             raise ValueError, "Cannot move '%s'  User Source up." % (id, )
02466         dest_index = src_index - 1
02467 
02468         # Find numbers to swap, fix them if they have more than 1 as offset
02469         if ids[dest_index] == 'Users':
02470             dest_num = 0
02471         else:
02472             dest_num = int(ids[dest_index][-2:])
02473         src_num = dest_num + 1
02474 
02475         # Get ids
02476         src_id = id
02477         if dest_num == 0:
02478             dest_id = "Users"
02479         else:
02480             dest_id = "Users%02d" % (dest_num,)
02481         tmp_id = "%s_" % (dest_id, )
02482 
02483         # Perform the swap
02484         self._renameUserSource(src_id, tmp_id)
02485         self._renameUserSource(dest_id, src_id)
02486         self._renameUserSource(tmp_id, dest_id)
02487 
02488         # Return back to the forms
02489         if REQUEST.has_key('RESPONSE'):
02490             return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
02491     moveUserSourceUp = postonly(moveUserSourceUp)
02492 
02493     security.declareProtected(Permissions.manage_users, "moveUserSourceDown")
02494     def moveUserSourceDown(self, id, REQUEST = {}):
02495         """
02496         moveUserSourceDown(self, id, REQUEST = {}) => used in management screens
02497         try to get ids as consistant as possible
02498         """
02499         # List and sort sources and preliminary checks
02500         ids = self.objectIds('GRUFUsers')
02501         ids.sort()
02502         if not ids or not id in ids:
02503             raise ValueError, "Invalid User Source: '%s'" % (id,)
02504 
02505         # Find indexes to swap
02506         src_index = ids.index(id)
02507         if src_index == len(ids) - 1:
02508             raise ValueError, "Cannot move '%s'  User Source up." % (id, )
02509         dest_index = src_index + 1
02510 
02511         # Find numbers to swap, fix them if they have more than 1 as offset
02512         if id == 'Users':
02513             dest_num = 1
02514         else:
02515             dest_num = int(ids[dest_index][-2:])
02516         src_num = dest_num - 1
02517 
02518         # Get ids
02519         src_id = id
02520         if dest_num == 0:
02521             dest_id = "Users"
02522         else:
02523             dest_id = "Users%02d" % (dest_num,)
02524         tmp_id = "%s_" % (dest_id, )
02525 
02526         # Perform the swap
02527         self._renameUserSource(src_id, tmp_id)
02528         self._renameUserSource(dest_id, src_id)
02529         self._renameUserSource(tmp_id, dest_id)
02530 
02531         # Return back to the forms
02532         if REQUEST.has_key('RESPONSE'):
02533             return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
02534     moveUserSourceDown = postonly(moveUserSourceDown)
02535 
02536 
02537     security.declarePrivate('_renameUserSource')
02538     def _renameUserSource(self, id, new_id, ):
02539         """
02540         Rename a particular sub-object.
02541         Taken fro CopySupport.manage_renameObject() code, modified to disable verifications.
02542         """
02543         try: self._checkId(new_id)
02544         except: raise CopyError, MessageDialog(
02545                       title='Invalid Id',
02546                       message=sys.exc_info()[1],
02547                       action ='manage_main')
02548         ob=self._getOb(id)
02549 ##        if not ob.cb_isMoveable():
02550 ##            raise "Copy Error", eNotSupported % id
02551 ##        self._verifyObjectPaste(ob)           # This is what we disable
02552         try:    ob._notifyOfCopyTo(self, op=1)
02553         except: raise CopyError, MessageDialog(
02554                       title='Rename Error',
02555                       message=sys.exc_info()[1],
02556                       action ='manage_main')
02557         self._delObject(id)
02558         ob = aq_base(ob)
02559         ob._setId(new_id)
02560 
02561         # Note - because a rename always keeps the same context, we
02562         # can just leave the ownership info unchanged.
02563         self._setObject(new_id, ob, set_owner=0)
02564 
02565 
02566     security.declareProtected(Permissions.manage_users, "replaceUserSource")
02567     def replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw):
02568         """
02569         replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw) => perform user source replacement
02570 
02571         If new_factory is None, find it inside REQUEST (useful for ZMI screens)
02572         """
02573         # Check the source id
02574         if type(id) != type('s'):
02575             raise ValueError, "You must choose a valid source to replace and confirm it."
02576 
02577         # Retreive factory if not explicitly passed
02578         if not new_factory:
02579             for record in REQUEST.get("source_rec", []):
02580                 if record['id'] == id:
02581                     new_factory = record['new_factory']
02582                     break
02583             if not new_factory:
02584                 raise ValueError, "You must select a new User Folder type."
02585 
02586         # Delete the former one
02587         us = getattr(self, id)
02588         if "acl_users" in us.objectIds():
02589             us.manage_delObjects(['acl_users'])
02590 
02591         ## If we use ldap, tag it
02592         #if string.find(new_factory.lower(), "ldap") > -1:
02593         #    self._haveLDAPUF += 1
02594 
02595         # Re-create the underlying UserFolder
02596         # If we're called TTW, uses a redirect else tries to call the UF factory directly
02597         if REQUEST.has_key('RESPONSE'):
02598             return REQUEST.RESPONSE.redirect("%s/%s/%s" % (self.absolute_url(), id, new_factory))
02599         return us.unrestrictedTraverse(new_factory)(*args, **kw) # XXX minor security pb ?
02600     replaceUserSource = postonly(replaceUserSource)
02601 
02602 
02603     security.declareProtected(Permissions.manage_users, "hasLDAPUserFolderSource")
02604     def hasLDAPUserFolderSource(self, ):
02605         """
02606         hasLDAPUserFolderSource(self,) => boolean
02607         Return true if a LUF source is instanciated.
02608         """
02609         for src in self.listUserSources():
02610             if src.meta_type == "LDAPUserFolder":
02611                 return 1
02612         return None
02613 
02614 
02615     security.declareProtected(Permissions.manage_users, "updateLDAPUserFolderMapping")
02616     def updateLDAPUserFolderMapping(self, REQUEST = None):
02617         """
02618         updateLDAPUserFolderMapping(self, REQUEST = None) => None
02619 
02620         Update the first LUF source in the process so that LDAP-group-to-Zope-role mapping
02621         is done.
02622         This is done by calling the appropriate method in LUF and affecting all 'group_' roles
02623         to the matching LDAP groups.
02624         """
02625         # Fetch all groups
02626         groups = self.getGroupIds()
02627 
02628         # Scan sources
02629         for src in self.listUserSources():
02630             if not src.meta_type == "LDAPUserFolder":
02631                 continue
02632 
02633             # Delete all former group mappings
02634             deletes = []
02635             for (grp, role) in src.getGroupMappings():
02636                 if role.startswith('group_'):
02637                     deletes.append(grp)
02638             src.manage_deleteGroupMappings(deletes)
02639 
02640             # Append all group mappings if it can be done
02641             ldap_groups = src.getGroups(attr = "cn")
02642             for grp in groups:
02643                 if src._local_groups:
02644                     grp_name = grp
02645                 else:
02646                     grp_name = grp[len('group_'):]
02647                 Log(LOG_DEBUG, "cheching", grp_name, "in", ldap_groups, )
02648                 if not grp_name in ldap_groups:
02649                     continue
02650                 Log(LOG_DEBUG, "Map", grp, "to", grp_name)
02651                 src.manage_addGroupMapping(
02652                     grp_name,
02653                     grp,
02654                     )
02655 
02656         # Return
02657         if REQUEST:
02658             return REQUEST.RESPONSE.redirect(
02659                 self.absolute_url() + "/manage_wizard",
02660                 )
02661         updateLDAPUserFolderMapping = postonly(updateLDAPUserFolderMapping)
02662 
02663 
02664     #                                                                               #
02665     #                               The Wizard Section                              #
02666     #                                                                               #
02667 
02668     def listLDAPUserFolderMapping(self,):
02669         """
02670         listLDAPUserFolderMapping(self,) => utility method
02671         """
02672         ret = []
02673         gruf_done = []
02674         ldap_done = []
02675 
02676         # Scan sources
02677         for src in self.listUserSources():
02678             if not src.meta_type == "LDAPUserFolder":
02679                 continue
02680 
02681             # Get all GRUF & LDAP groups
02682             if src._local_groups:
02683                 gruf_ids = self.getGroupIds()
02684             else:
02685                 gruf_ids = self.getGroupIds()
02686             ldap_mapping = src.getGroupMappings()
02687             ldap_groups = src.getGroups(attr = "cn")
02688             for grp,role in ldap_mapping:
02689                 if role in gruf_ids:
02690                     ret.append((role, grp))
02691                     gruf_done.append(role)
02692                     ldap_done.append(grp)
02693                     if not src._local_groups:
02694                         ldap_done.append(role)
02695             for grp in ldap_groups:
02696                 if not grp in ldap_done:
02697                     ret.append((None, grp))
02698             for grp in gruf_ids:
02699                 if not grp in gruf_done:
02700                     ret.append((grp, None))
02701             Log(LOG_DEBUG, "return", ret)
02702             return ret
02703 
02704 
02705     security.declareProtected(Permissions.manage_users, "getInvalidMappings")
02706     def getInvalidMappings(self,):
02707         """
02708         return true if LUF mapping looks good
02709         """
02710         wrong = []
02711         grufs = []
02712         for gruf, ldap in self.listLDAPUserFolderMapping():
02713             if gruf and ldap:
02714                 continue
02715             if not gruf:
02716                 continue
02717             if gruf.startswith('group_'):
02718                 gruf = gruf[len('group_'):]
02719             grufs.append(gruf)
02720         for gruf, ldap in self.listLDAPUserFolderMapping():
02721             if gruf and ldap:
02722                 continue
02723             if not ldap:
02724                 continue
02725             if ldap.startswith('group_'):
02726                 ldap = ldap[len('group_'):]
02727             if ldap in grufs:
02728                 wrong.append(ldap)
02729 
02730         return wrong
02731 
02732     security.declareProtected(Permissions.manage_users, "getLUFSource")
02733     def getLUFSource(self,):
02734         """
02735         getLUFSource(self,) => Helper to get a pointer to the LUF src.
02736         Return None if not available
02737         """
02738         for src in self.listUserSources():
02739             if src.meta_type == "LDAPUserFolder":
02740                 return src
02741 
02742     security.declareProtected(Permissions.manage_users, "areLUFGroupsLocal")
02743     def areLUFGroupsLocal(self,):
02744         """return true if luf groups are stored locally"""
02745         return hasattr(self.getLUFSource(), '_local_groups')
02746 
02747 
02748     security.declareProtected(Permissions.manage_users, "haveLDAPGroupFolder")
02749     def haveLDAPGroupFolder(self,):
02750         """return true if LDAPGroupFolder is the groups source
02751         """
02752         return not not self.Groups.acl_users.meta_type == 'LDAPGroupFolder'
02753 
02754 
02755 class treeWrapper:
02756     """
02757     treeWrapper: Wrapper around user/group objects for the tree
02758     """
02759     def __init__(self, id, tree, parents = []):
02760         """
02761         __init__(self, id, tree, parents = []) => wraps the user object for dtml-tree
02762         """
02763         # Prepare self-contained information
02764         self._id = id
02765         self.name = tree[id]['name']
02766         self.icon = tree[id]['icon']
02767         self.is_group = tree[id]['is_group']
02768         parents.append(id)
02769         self.path = parents
02770 
02771         # Prepare subobjects information
02772         subobjects = []
02773         for grp_id in tree.keys():
02774             if id in tree[grp_id]['belongs_to']:
02775                 subobjects.append(treeWrapper(grp_id, tree, parents))
02776         subobjects.sort(lambda x, y: cmp(x.sortId(), y.sortId()))
02777         self.subobjects = subobjects
02778 
02779     def id(self,):
02780         return self.name
02781 
02782     def sortId(self,):
02783         if self.is_group:
02784             return "__%s" % (self._id,)
02785         else:
02786             return self._id
02787 
02788     def tpValues(self,):
02789         """
02790         Return 'subobjects'
02791         """
02792         return self.subobjects
02793 
02794     def tpId(self,):
02795         return self._id
02796 
02797     def tpURL(self,):
02798         return self.tpId()
02799 
02800 InitializeClass(GroupUserFolder)