+# -*- coding: utf-8 -*-
+## GroupUserFolder
+## Copyright (C)2006 Ingeniweb
+
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, write to the
+## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+GroupUserFolder product
+"""
+__version__ = "$Revision: $"
+# $Source: $
+# $Id: GroupUserFolder.py 40118 2007-04-01 15:13:44Z alecm $
+__docformat__ = 'restructuredtext'
+
+
+# fakes a method from a DTML file
+from Globals import MessageDialog, DTMLFile
+
+from AccessControl import ClassSecurityInfo
+from AccessControl import Permissions
+from AccessControl import getSecurityManager
+from AccessControl import Unauthorized
+from Globals import InitializeClass
+from Acquisition import aq_base, aq_inner, aq_parent
+from Acquisition import Implicit
+from Globals import Persistent
+from AccessControl.Role import RoleManager
+from OFS.SimpleItem import Item
+from OFS.PropertyManager import PropertyManager
+import OFS
+from OFS import ObjectManager, SimpleItem
+from DateTime import DateTime
+from App import ImageFile
+from Products.PageTemplates import PageTemplateFile
+import AccessControl.Role, webdav.Collection
+import Products
+import os
+import string
+import sys
+import time
+import math
+import random
+from global_symbols import *
+import AccessControl.User
+import GRUFFolder
+import GRUFUser
+from Products.PageTemplates import PageTemplateFile
+import class_utility
+from Products.GroupUserFolder import postonly
+
+from interfaces.IUserFolder import IUserFolder
+
+## Developers notes
+##
+## The REQUEST.GRUF_PROBLEM variable is defined whenever GRUF encounters
+## a problem than can be showed in the management screens. It's always
+## logged as LOG_WARNING level anyway.
+
+_marker = []
+
+def unique(sequence, _list = 0):
+ """Make a sequence a list of unique items"""
+ uniquedict = {}
+ for v in sequence:
+ uniquedict[v] = 1
+ if _list:
+ return list(uniquedict.keys())
+ return tuple(uniquedict.keys())
+
+
+def manage_addGroupUserFolder(self, dtself=None, REQUEST=None, **ignored):
+ """ Factory method that creates a UserFolder"""
+ f=GroupUserFolder()
+ self=self.this()
+ try: self._setObject('acl_users', f)
+ except: return MessageDialog(
+ title ='Item Exists',
+ message='This object already contains a User Folder',
+ action ='%s/manage_main' % REQUEST['URL1'])
+ self.__allow_groups__=f
+ self.acl_users._post_init()
+
+ self.acl_users.Users.manage_addUserFolder()
+ self.acl_users.Groups.manage_addUserFolder()
+
+ if REQUEST is not None:
+ REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+
+
+
+
+class GroupUserFolder(OFS.ObjectManager.ObjectManager,
+ AccessControl.User.BasicUserFolder,
+ ):
+ """
+ GroupUserFolder => User folder with groups management
+ """
+
+ # #
+ # ZOPE INFORMATION #
+ # #
+
+ meta_type='Group User Folder'
+ id ='acl_users'
+ title ='Group-aware User Folder'
+
+ __implements__ = (IUserFolder, )
+ def __creatable_by_emergency_user__(self): return 1
+
+ isAnObjectManager = 1
+ isPrincipiaFolderish = 1
+ isAUserFolder = 1
+
+## _haveLDAPUF = 0
+
+ security = ClassSecurityInfo()
+
+ manage_options=(
+ (
+ {'label':'Overview', 'action':'manage_overview'},
+ {'label':'Sources', 'action':'manage_GRUFSources'},
+ {'label':'LDAP Wizard', 'action':'manage_wizard'},
+ {'label':'Groups', 'action':'manage_groups'},
+ {'label':'Users', 'action':'manage_users'},
+ {'label':'Audit', 'action':'manage_audit'},
+ ) + \
+ OFS.ObjectManager.ObjectManager.manage_options + \
+ RoleManager.manage_options + \
+ Item.manage_options )
+
+ manage_main = OFS.ObjectManager.ObjectManager.manage_main
+## manage_overview = DTMLFile('dtml/GRUF_overview', globals())
+ manage_overview = PageTemplateFile.PageTemplateFile('dtml/GRUF_overview', globals())
+ manage_audit = PageTemplateFile.PageTemplateFile('dtml/GRUF_audit', globals())
+ manage_wizard = PageTemplateFile.PageTemplateFile('dtml/GRUF_wizard', globals())
+ manage_groups = PageTemplateFile.PageTemplateFile('dtml/GRUF_groups', globals())
+ manage_users = PageTemplateFile.PageTemplateFile('dtml/GRUF_users', globals())
+ manage_newusers = PageTemplateFile.PageTemplateFile('dtml/GRUF_newusers', globals())
+ manage_GRUFSources = PageTemplateFile.PageTemplateFile('dtml/GRUF_contents', globals())
+ manage_user = PageTemplateFile.PageTemplateFile('dtml/GRUF_user', globals())
+
+ __ac_permissions__=(
+ ('Manage users',
+ ('manage_users',
+ 'user_names', 'setDomainAuthenticationMode',
+ )
+ ),
+ )
+
+
+ # Color constants, only useful within GRUF management screens
+ user_color = "#006600"
+ group_color = "#000099"
+ role_color = "#660000"
+
+ # User and group images
+ img_user = ImageFile.ImageFile('www/GRUFUsers.gif', globals())
+ img_group = ImageFile.ImageFile('www/GRUFGroups.gif', globals())
+
+
+
+ # #
+ # OFFICIAL INTERFACE #
+ # #
+
+ security.declarePublic("hasUsers")
+ def hasUsers(self, ):
+ """
+ From Zope 2.7's User.py:
+ This is not a formal API method: it is used only to provide
+ a way for the quickstart page to determine if the default user
+ folder contains any users to provide instructions on how to
+ add a user for newbies. Using getUserNames or getUsers would have
+ posed a denial of service risk.
+ In GRUF, this method always return 1."""
+ return 1
+
+ security.declareProtected(Permissions.manage_users, "user_names")
+ def user_names(self,):
+ """
+ user_names() => return user IDS and not user NAMES !!!
+ Due to a Zope inconsistency, the Role.get_valid_userids return user names
+ and not user ids - which is bad. As GRUF distinguishes names and ids, this
+ will cause it to break, especially in the listLocalRoles form. So we change
+ user_names() behaviour so that it will return ids and not names.
+ """
+ return self.getUserIds()
+
+
+ security.declareProtected(Permissions.manage_users, "getUserNames")
+ def getUserNames(self, __include_groups__ = 1, __include_users__ = 1, __groups_prefixed__ = 0):
+ """
+ Return a list of all possible user atom names in the system.
+ Groups will be returned WITHOUT their prefix by this method.
+ So, there might be a collision between a user name and a group name.
+ [NOTA: This method is time-expensive !]
+ """
+ if __include_users__:
+ LogCallStack(LOG_DEBUG, "This call can be VERY expensive!")
+ names = []
+ ldap_sources = []
+
+ # Fetch users in user sources
+ if __include_users__:
+ for src in self.listUserSources():
+ names.extend(src.getUserNames())
+
+ # Append groups if possible
+ if __include_groups__:
+ # Regular groups
+ if "acl_users" in self._getOb('Groups').objectIds():
+ names.extend(self.Groups.listGroups(prefixed = __groups_prefixed__))
+
+ # LDAP groups
+ for ldapuf in ldap_sources:
+ if ldapuf._local_groups:
+ continue
+ for g in ldapuf.getGroups(attr = LDAP_GROUP_RDN):
+ if __groups_prefixed__:
+ names.append("%s%s" % (GROUP_PREFIX, g))
+ else:
+ names.append(g)
+ # Return a list of unique names
+ return unique(names, _list = 1)
+
+ security.declareProtected(Permissions.manage_users, "getUserIds")
+ def getUserIds(self,):
+ """
+ Return a list of all possible user atom ids in the system.
+ WARNING: Please see the id Vs. name consideration at the
+ top of this document. So, groups will be returned
+ WITH their prefix by this method
+ [NOTA: This method is time-expensive !]
+ """
+ return self.getUserNames(__groups_prefixed__ = 1)
+
+ security.declareProtected(Permissions.manage_users, "getUsers")
+ def getUsers(self, __include_groups__ = 1, __include_users__ = 1):
+ """Return a list of user and group objects.
+ In case of some UF implementations, the returned object may only be a subset
+ of all possible users.
+ In other words, you CANNOT assert that len(getUsers()) equals len(getUserNames()).
+ With cache-support UserFolders, such as LDAPUserFolder, the getUser() method will
+ return only cached user objects instead of fetching all possible users.
+ """
+ Log(LOG_DEBUG, "getUsers")
+ ret = []
+ names_set = {}
+
+ # avoid too many lookups for 'has_key' in loops
+ isUserProcessed = names_set.has_key
+
+ # Fetch groups first (then the user must be
+ # prefixed by 'group_' prefix)
+ if __include_groups__:
+ # Fetch regular groups
+ for u in self._getOb('Groups').acl_users.getUsers():
+ if not u:
+ continue # Ignore empty users
+
+ name = u.getId()
+ if isUserProcessed(name):
+ continue # Prevent double users inclusion
+
+ # Append group
+ names_set[name] = True
+ ret.append(
+ GRUFUser.GRUFGroup(u, self, isGroup = 1, source_id = "Groups").__of__(self)
+ )
+
+ # Fetch users then
+ if __include_users__:
+ for src in self.listUserSources():
+ for u in src.getUsers():
+ if not u:
+ continue # Ignore empty users
+
+ name = u.getId()
+ if isUserProcessed(name):
+ continue # Prevent double users inclusion
+
+ # Append user
+ names_set[name] = True
+ ret.append(
+ GRUFUser.GRUFUser(u, self, source_id = src.getUserSourceId(), isGroup = 0).__of__(self)
+ )
+
+ return tuple(ret)
+
+ security.declareProtected(Permissions.manage_users, "getUser")
+ def getUser(self, name, __include_users__ = 1, __include_groups__ = 1, __force_group_id__ = 0):
+ """
+ Return the named user object or None.
+ User have precedence over group.
+ If name is None, getUser() will return None.
+ """
+ # Basic check
+ if name is None:
+ return None
+
+ # Prevent infinite recursion when instanciating a GRUF
+ # without having sub-acl_users set
+ if not "acl_users" in self._getOb('Groups').objectIds():
+ return None
+
+ # Fetch groups first (then the user must be prefixed by 'group_' prefix)
+ if __include_groups__ and name.startswith(GROUP_PREFIX):
+ id = name[GROUP_PREFIX_LEN:]
+
+ # Fetch regular groups
+ u = self._getOb('Groups')._getGroup(id)
+ if u:
+ ret = GRUFUser.GRUFGroup(
+ u, self, isGroup = 1, source_id = "Groups"
+ ).__of__(self)
+ return ret # XXX This violates precedence
+
+ # Fetch users then
+ if __include_users__:
+ for src in self.listUserSources():
+ u = src.getUser(name)
+ if u:
+ ret = GRUFUser.GRUFUser(u, self, source_id = src.getUserSourceId(), isGroup = 0).__of__(self)
+ return ret
+
+ # Then desperatly try to fetch groups (without beeing prefixed by 'group_' prefix)
+ if __include_groups__ and (not __force_group_id__):
+ u = self._getOb('Groups')._getGroup(name)
+ if u:
+ ret = GRUFUser.GRUFGroup(u, self, isGroup = 1, source_id = "Groups").__of__(self)
+ return ret
+
+ return None
+
+
+ security.declareProtected(Permissions.manage_users, "getUserById")
+ def getUserById(self, id, default=_marker):
+ """Return the user atom corresponding to the given id. Can return groups.
+ """
+ ret = self.getUser(id, __force_group_id__ = 1)
+ if not ret:
+ if default is _marker:
+ return None
+ ret = default
+ return ret
+
+
+ security.declareProtected(Permissions.manage_users, "getUserByName")
+ def getUserByName(self, name, default=_marker):
+ """Same as getUser() but works with a name instead of an id.
+ [NOTA: Theorically, the id is a handle, while the name is the actual login name.
+ But difference between a user id and a user name is unsignificant in
+ all current User Folder implementations... except for GROUPS.]
+ """
+ # Try to fetch a user first
+ usr = self.getUser(name)
+
+ # If not found, try to fetch a group by appending the prefix
+ if not usr:
+ name = "%s%s" % (GROUP_PREFIX, name)
+ usr = self.getUserById(name, default)
+
+ return usr
+
+ security.declareProtected(Permissions.manage_users, "getPureUserNames")
+ def getPureUserNames(self, ):
+ """Fetch the list of actual users from GRUFUsers.
+ """
+ return self.getUserNames(__include_groups__ = 0)
+
+
+ security.declareProtected(Permissions.manage_users, "getPureUserIds")
+ def getPureUserIds(self,):
+ """Same as getUserIds() but without groups
+ """
+ return self.getUserNames(__include_groups__ = 0)
+
+ security.declareProtected(Permissions.manage_users, "getPureUsers")
+ def getPureUsers(self):
+ """Return a list of pure user objects.
+ """
+ return self.getUsers(__include_groups__ = 0)
+
+ security.declareProtected(Permissions.manage_users, "getPureUser")
+ def getPureUser(self, id, ):
+ """Return the named user object or None"""
+ # Performance tricks
+ if not id:
+ return None
+
+ # Fetch it
+ return self.getUser(id, __include_groups__ = 0)
+
+
+ security.declareProtected(Permissions.manage_users, "getGroupNames")
+ def getGroupNames(self, ):
+ """Same as getUserNames() but without pure users.
+ """
+ return self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 0)
+
+ security.declareProtected(Permissions.manage_users, "getGroupIds")
+ def getGroupIds(self, ):
+ """Same as getUserNames() but without pure users.
+ """
+ return self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 1)
+
+ security.declareProtected(Permissions.manage_users, "getGroups")
+ def getGroups(self):
+ """Same as getUsers() but without pure users.
+ """
+ return self.getUsers(__include_users__ = 0)
+
+ security.declareProtected(Permissions.manage_users, "getGroup")
+ def getGroup(self, name, prefixed = 1):
+ """Return the named user object or None"""
+ # Performance tricks
+ if not name:
+ return None
+
+ # Unprefix group name
+ if not name.startswith(GROUP_PREFIX):
+ name = "%s%s" % (GROUP_PREFIX, name, )
+
+ # Fetch it
+ return self.getUser(name, __include_users__ = 0)
+
+ security.declareProtected(Permissions.manage_users, "getGroupById")
+ def getGroupById(self, id, default = _marker):
+ """Same as getUserById(id) but forces returning a group.
+ """
+ ret = self.getUser(id, __include_users__ = 0, __force_group_id__ = 1)
+ if not ret:
+ if default is _marker:
+ return None
+ ret = default
+ return ret
+
+ security.declareProtected(Permissions.manage_users, "getGroupByName")
+ def getGroupByName(self, name, default = _marker):
+ """Same as getUserByName(name) but forces returning a group.
+ """
+ ret = self.getUser(name, __include_users__ = 0, __force_group_id__ = 0)
+ if not ret:
+ if default is _marker:
+ return None
+ ret = default
+ return ret
+
+
+
+ # #
+ # REGULAR MUTATORS #
+ # #
+
+ security.declareProtected(Permissions.manage_users, "userFolderAddUser")
+ def userFolderAddUser(self, name, password, roles, domains, groups = (),
+ REQUEST=None, **kw):
+ """API method for creating a new user object. Note that not all
+ user folder implementations support dynamic creation of user
+ objects.
+ """
+ return self._doAddUser(name, password, roles, domains, groups, **kw)
+ userFolderAddUser = postonly(userFolderAddUser)
+
+ security.declareProtected(Permissions.manage_users, "userFolderEditUser")
+ def userFolderEditUser(self, name, password, roles, domains, groups = None,
+ REQUEST=None, **kw):
+ """API method for changing user object attributes. Note that not
+ all user folder implementations support changing of user object
+ attributes.
+ Arguments ARE required.
+ """
+ return self._doChangeUser(name, password, roles, domains, groups, **kw)
+ userFolderEditUser = postonly(userFolderEditUser)
+
+ security.declareProtected(Permissions.manage_users, "userFolderUpdateUser")
+ def userFolderUpdateUser(self, name, password = None, roles = None,
+ domains = None, groups = None, REQUEST=None, **kw):
+ """API method for changing user object attributes. Note that not
+ all user folder implementations support changing of user object
+ attributes.
+ Arguments are optional"""
+ return self._updateUser(name, password, roles, domains, groups, **kw)
+ userFolderUpdateUser = postonly(userFolderUpdateUser)
+
+ security.declareProtected(Permissions.manage_users, "userFolderDelUsers")
+ def userFolderDelUsers(self, names, REQUEST=None):
+ """API method for deleting one or more user atom objects. Note that not
+ all user folder implementations support deletion of user objects."""
+ return self._doDelUsers(names)
+ userFolderDelUsers = postonly(userFolderDelUsers)
+
+ security.declareProtected(Permissions.manage_users, "userFolderAddGroup")
+ def userFolderAddGroup(self, name, roles, groups = (), REQUEST=None, **kw):
+ """API method for creating a new group.
+ """
+ while name.startswith(GROUP_PREFIX):
+ name = name[GROUP_PREFIX_LEN:]
+ return self._doAddGroup(name, roles, groups, **kw)
+ userFolderAddGroup = postonly(userFolderAddGroup)
+
+ security.declareProtected(Permissions.manage_users, "userFolderEditGroup")
+ def userFolderEditGroup(self, name, roles, groups = None, REQUEST=None,
+ **kw):
+ """API method for changing group object attributes.
+ """
+ return self._doChangeGroup(name, roles = roles, groups = groups, **kw)
+ userFolderEditGroup = postonly(userFolderEditGroup)
+
+ security.declareProtected(Permissions.manage_users, "userFolderUpdateGroup")
+ def userFolderUpdateGroup(self, name, roles = None, groups = None,
+ REQUEST=None, **kw):
+ """API method for changing group object attributes.
+ """
+ return self._updateGroup(name, roles = roles, groups = groups, **kw)
+ userFolderUpdateGroup = postonly(userFolderUpdateGroup)
+
+ security.declareProtected(Permissions.manage_users, "userFolderDelGroups")
+ def userFolderDelGroups(self, names, REQUEST=None):
+ """API method for deleting one or more group objects.
+ Implem. note : All ids must be prefixed with 'group_',
+ so this method ends up beeing only a filter of non-prefixed ids
+ before calling userFolderDelUsers().
+ """
+ return self._doDelGroups(names)
+ userFolderDelUsers = postonly(userFolderDelUsers)
+
+
+
+ # #
+ # SEARCH METHODS #
+ # #
+
+
+ security.declareProtected(Permissions.manage_users, "searchUsersByAttribute")
+ def searchUsersByAttribute(self, attribute, search_term):
+ """Return user ids whose 'attribute' match the specified search_term.
+ If search_term is an empty string, behaviour depends on the underlying user folder:
+ it may return all users, return only cached users (for LDAPUF) or return no users.
+ This will return all users whose name contains search_term (whaterver its case).
+ THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
+ SEARCHING METHOD (ie. every UF kind except LDAPUF).
+ 'attribute' can be 'id' or 'name' for all UF kinds, or anything else for LDAPUF.
+ """
+ ret = []
+ for src in self.listUserSources():
+ # Use source-specific search methods if available
+ if hasattr(src.aq_base, "findUser"):
+ # LDAPUF
+ Log(LOG_DEBUG, "We use LDAPUF to find users")
+ id_attr = src._uid_attr
+ if attribute == 'name':
+ attr = src._login_attr
+ elif attribute == 'id':
+ attr = src._uid_attr
+ else:
+ attr = attribute
+ Log(LOG_DEBUG, "we use findUser", attr, search_term, )
+ users = src.findUser(attr, search_term, exact_match = True)
+ ret.extend(
+ [ u[id_attr] for u in users ],
+ )
+ else:
+ # Other types of user folder
+ search_term = search_term.lower()
+
+ # Find the proper method according to the attribute type
+ if attribute == "name":
+ method = "getName"
+ elif attribute == "id":
+ method = "getId"
+ else:
+ raise NotImplementedError, "Attribute searching is only supported for LDAPUserFolder by now."
+
+ # Actually search
+ src_id = src.getUserSourceId()
+ for u in src.getUsers():
+ if not u:
+ continue
+ u = GRUFUser.GRUFUser(u, self, source_id=src_id,
+ isGroup=0).__of__(self)
+ s = getattr(u, method)().lower()
+ if string.find(s, search_term) != -1:
+ ret.append(u.getId())
+ Log(LOG_DEBUG, "We've found them:", ret)
+ return ret
+
+ security.declareProtected(Permissions.manage_users, "searchUsersByName")
+ def searchUsersByName(self, search_term):
+ """Return user ids whose name match the specified search_term.
+ If search_term is an empty string, behaviour depends on the underlying user folder:
+ it may return all users, return only cached users (for LDAPUF) or return no users.
+ This will return all users whose name contains search_term (whaterver its case).
+ THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
+ SEARCHING METHOD (ie. every UF kind except LDAPUF)
+ """
+ return self.searchUsersByAttribute("name", search_term)
+
+ security.declareProtected(Permissions.manage_users, "searchUsersById")
+ def searchUsersById(self, search_term):
+ """Return user ids whose id match the specified search_term.
+ If search_term is an empty string, behaviour depends on the underlying user folder:
+ it may return all users, return only cached users (for LDAPUF) or return no users.
+ This will return all users whose name contains search_term (whaterver its case).
+ THIS METHOD MAY BE VERY EXPENSIVE ON USER FOLDER KINDS WHICH DO NOT PROVIDE A
+ SEARCHING METHOD (ie. every UF kind except LDAPUF)
+ """
+ return self.searchUsersByAttribute("id", search_term)
+
+
+ security.declareProtected(Permissions.manage_users, "searchGroupsByAttribute")
+ def searchGroupsByAttribute(self, attribute, search_term):
+ """Return group ids whose 'attribute' match the specified search_term.
+ If search_term is an empty string, behaviour depends on the underlying group folder:
+ it may return all groups, return only cached groups (for LDAPUF) or return no groups.
+ This will return all groups whose name contains search_term (whaterver its case).
+ THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
+ SEARCHING METHOD (ie. every UF kind except LDAPUF).
+ 'attribute' can be 'id' or 'name' for all UF kinds, or anything else for LDAPUF.
+ """
+ ret = []
+ src = self.Groups
+
+ # Use source-specific search methods if available
+ if hasattr(src.aq_base, "findGroup"):
+ # LDAPUF
+ id_attr = src._uid_attr
+ if attribute == 'name':
+ attr = src._login_attr
+ elif attribute == 'id':
+ attr = src._uid_attr
+ else:
+ attr = attribute
+ groups = src.findGroup(attr, search_term)
+ ret.extend(
+ [ u[id_attr] for u in groups ],
+ )
+ else:
+ # Other types of group folder
+ search_term = search_term.lower()
+
+ # Find the proper method according to the attribute type
+ if attribute == "name":
+ method = "getName"
+ elif attribute == "id":
+ method = "getId"
+ else:
+ raise NotImplementedError, "Attribute searching is only supported for LDAPGroupFolder by now."
+
+ # Actually search
+ for u in self.getGroups():
+ s = getattr(u, method)().lower()
+ if string.find(s, search_term) != -1:
+ ret.append(u.getId())
+ return ret
+
+ security.declareProtected(Permissions.manage_users, "searchGroupsByName")
+ def searchGroupsByName(self, search_term):
+ """Return group ids whose name match the specified search_term.
+ If search_term is an empty string, behaviour depends on the underlying group folder:
+ it may return all groups, return only cached groups (for LDAPUF) or return no groups.
+ This will return all groups whose name contains search_term (whaterver its case).
+ THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
+ SEARCHING METHOD (ie. every UF kind except LDAPUF)
+ """
+ return self.searchGroupsByAttribute("name", search_term)
+
+ security.declareProtected(Permissions.manage_users, "searchGroupsById")
+ def searchGroupsById(self, search_term):
+ """Return group ids whose id match the specified search_term.
+ If search_term is an empty string, behaviour depends on the underlying group folder:
+ it may return all groups, return only cached groups (for LDAPUF) or return no groups.
+ This will return all groups whose name contains search_term (whaterver its case).
+ THIS METHOD MAY BE VERY EXPENSIVE ON GROUP FOLDER KINDS WHICH DO NOT PROVIDE A
+ SEARCHING METHOD (ie. every UF kind except LDAPUF)
+ """
+ return self.searchGroupsByAttribute("id", search_term)
+
+ # #
+ # SECURITY MANAGEMENT METHODS #
+ # #
+
+ security.declareProtected(Permissions.manage_users, "setRolesOnUsers")
+ def setRolesOnUsers(self, roles, userids, REQUEST = None):
+ """Set a common set of roles for a bunch of user atoms.
+ """
+ for usr in userids:
+ self.userSetRoles(usr, roles)
+ setRolesOnUsers = postonly(setRolesOnUsers)
+
+## def setUsersOfRole(self, usernames, role):
+## """Sets the users of a role.
+## XXX THIS METHOD SEEMS TO BE SEAMLESS.
+## """
+## raise NotImplementedError, "Not implemented."
+
+ security.declareProtected(Permissions.manage_users, "getUsersOfRole")
+ def getUsersOfRole(self, role, object = None):
+ """Gets the user (and group) ids having the specified role...
+ ...on the specified Zope object if it's not None
+ ...on their own information if the object is None.
+ NOTA: THIS METHOD IS VERY EXPENSIVE.
+ XXX PERFORMANCES HAVE TO BE IMPROVED
+ """
+ ret = []
+ for id in self.getUserIds():
+ if role in self.getRolesOfUser(id):
+ ret.append(id)
+ return tuple(ret)
+
+ security.declarePublic("getRolesOfUser")
+ def getRolesOfUser(self, userid):
+ """Alias for user.getRoles()
+ """
+ return self.getUserById(userid).getRoles()
+
+ security.declareProtected(Permissions.manage_users, "userFolderAddRole")
+ def userFolderAddRole(self, role, REQUEST=None):
+ """Add a new role. The role will be appended, in fact, in GRUF's surrounding folder.
+ """
+ if role in self.aq_parent.valid_roles():
+ raise ValueError, "Role '%s' already exist" % (role, )
+
+ return self.aq_parent._addRole(role)
+ userFolderAddRole = postonly(userFolderAddRole)
+
+ security.declareProtected(Permissions.manage_users, "userFolderDelRoles")
+ def userFolderDelRoles(self, roles, REQUEST=None):
+ """Delete roles.
+ The removed roles will be removed from the UserFolder's users and groups as well,
+ so this method can be very time consuming with a large number of users.
+ """
+ # Check that roles exist
+ ud_roles = self.aq_parent.userdefined_roles()
+ for r in roles:
+ if not r in ud_roles:
+ raise ValueError, "Role '%s' is not defined on acl_users' parent folder" % (r, )
+
+ # Remove role on all users
+ for r in roles:
+ for u in self.getUsersOfRole(r, ):
+ self.userRemoveRole(u, r, )
+
+ # Actually remove role
+ return self.aq_parent._delRoles(roles, None)
+ userFolderDelRoles = postonly(userFolderDelRoles)
+
+ security.declarePublic("userFolderGetRoles")
+ def userFolderGetRoles(self, ):
+ """
+ userFolderGetRoles(self,) => tuple of strings
+ List the roles defined at the top of GRUF's folder.
+ This includes both user-defined roles and default roles.
+ """
+ return tuple(self.aq_parent.valid_roles())
+
+
+ # Groups support
+
+ security.declareProtected(Permissions.manage_users, "setMembers")
+ def setMembers(self, groupid, userids, REQUEST=None):
+ """Set the members of the group
+ """
+ self.getGroup(groupid).setMembers(userids)
+ setMembers = postonly(setMembers)
+
+ security.declareProtected(Permissions.manage_users, "addMember")
+ def addMember(self, groupid, userid, REQUEST=None):
+ """Add a member to a group
+ """
+ return self.getGroup(groupid).addMember(userid)
+ addMember = postonly(addMember)
+
+ security.declareProtected(Permissions.manage_users, "removeMember")
+ def removeMember(self, groupid, userid, REQUEST=None):
+ """Remove a member from a group.
+ """
+ return self.getGroup(groupid).removeMember(userid)
+ removeMember = postonly(removeMember)
+
+ security.declareProtected(Permissions.manage_users, "getMemberIds")
+ def getMemberIds(self, groupid):
+ """Return the list of member ids (groups and users) in this group
+ """
+ m = self.getGroup(groupid)
+ if not m:
+ raise ValueError, "Invalid group: '%s'" % groupid
+ return self.getGroup(groupid).getMemberIds()
+
+ security.declareProtected(Permissions.manage_users, "getUserMemberIds")
+ def getUserMemberIds(self, groupid):
+ """Return the list of member ids (groups and users) in this group
+ """
+ return self.getGroup(groupid).getUserMemberIds()
+
+ security.declareProtected(Permissions.manage_users, "getGroupMemberIds")
+ def getGroupMemberIds(self, groupid):
+ """Return the list of member ids (groups and users) in this group
+ XXX THIS MAY BE VERY EXPENSIVE !
+ """
+ return self.getGroup(groupid).getGroupMemberIds()
+
+ security.declareProtected(Permissions.manage_users, "hasMember")
+ def hasMember(self, groupid, id):
+ """Return true if the specified atom id is in the group.
+ This is the contrary of IUserAtom.isInGroup(groupid).
+ THIS CAN BE VERY EXPENSIVE
+ """
+ return self.getGroup(groupid).hasMember(id)
+
+
+ # User mutation
+
+## def setUserId(id, newId):
+## """Change id of a user atom.
+## """
+
+## def setUserName(id, newName):
+## """Change the name of a user atom.
+## """
+
+ security.declareProtected(Permissions.manage_users, "userSetRoles")
+ def userSetRoles(self, id, roles, REQUEST=None):
+ """Change the roles of a user atom.
+ """
+ self._updateUser(id, roles = roles)
+ userSetRoles = postonly(userSetRoles)
+
+ security.declareProtected(Permissions.manage_users, "userAddRole")
+ def userAddRole(self, id, role, REQUEST=None):
+ """Append a role for a user atom
+ """
+ roles = list(self.getUser(id).getRoles())
+ if not role in roles:
+ roles.append(role)
+ self._updateUser(id, roles = roles)
+ userAddRole = postonly(userAddRole)
+
+ security.declareProtected(Permissions.manage_users, "userRemoveRole")
+ def userRemoveRole(self, id, role, REQUEST=None):
+ """Remove the role of a user atom. Will NOT complain if role doesn't exist
+ """
+ roles = list(self.getRolesOfUser(id))
+ if role in roles:
+ roles.remove(role)
+ self._updateUser(id, roles = roles)
+ userRemoveRole = postonly(userRemoveRole)
+
+ security.declareProtected(Permissions.manage_users, "userSetPassword")
+ def userSetPassword(self, id, newPassword, REQUEST=None):
+ """Set the password of a user
+ """
+ u = self.getPureUser(id)
+ if not u:
+ raise ValueError, "Invalid pure user id: '%s'" % (id,)
+ self._updateUser(u.getId(), password = newPassword, )
+ userSetPassword = postonly(userSetPassword)
+
+ security.declareProtected(Permissions.manage_users, "userGetDomains")
+ def userGetDomains(self, id):
+ """get domains for a user
+ """
+ usr = self.getPureUser(id)
+ return tuple(usr.getDomains())
+
+ security.declareProtected(Permissions.manage_users, "userSetDomains")
+ def userSetDomains(self, id, domains, REQUEST=None):
+ """Set domains for a user
+ """
+ usr = self.getPureUser(id)
+ self._updateUser(usr.getId(), domains = domains, )
+ userSetDomains = postonly(userSetDomains)
+
+ security.declareProtected(Permissions.manage_users, "userAddDomain")
+ def userAddDomain(self, id, domain, REQUEST=None):
+ """Append a domain to a user
+ """
+ usr = self.getPureUser(id)
+ domains = list(usr.getDomains())
+ if not domain in domains:
+ roles.append(domain)
+ self._updateUser(usr.getId(), domains = domains, )
+ userAddDomain = postonly(userAddDomain)
+
+ security.declareProtected(Permissions.manage_users, "userRemoveDomain")
+ def userRemoveDomain(self, id, domain, REQUEST=None):
+ """Remove a domain from a user
+ """
+ usr = self.getPureUser(id)
+ domains = list(usr.getDomains())
+ if not domain in domains:
+ raise ValueError, "User '%s' doesn't have domain '%s'" % (id, domain, )
+ while domain in domains:
+ roles.remove(domain)
+ self._updateUser(usr.getId(), domains = domains)
+ userRemoveDomain = postonly(userRemoveDomain)
+
+ security.declareProtected(Permissions.manage_users, "userSetGroups")
+ def userSetGroups(self, id, groupnames, REQUEST=None):
+ """Set the groups of a user
+ """
+ self._updateUser(id, groups = groupnames)
+ userSetGroups = postonly(userSetGroups)
+
+ security.declareProtected(Permissions.manage_users, "userAddGroup")
+ def userAddGroup(self, id, groupname, REQUEST=None):
+ """add a group to a user atom
+ """
+ groups = list(self.getUserById(id).getGroups())
+ if not groupname in groups:
+ groups.append(groupname)
+ self._updateUser(id, groups = groups)
+ userAddGroup = postonly(userAddGroup)
+
+
+ security.declareProtected(Permissions.manage_users, "userRemoveGroup")
+ def userRemoveGroup(self, id, groupname, REQUEST=None):
+ """remove a group from a user atom.
+ """
+ groups = list(self.getUserById(id).getGroupNames())
+ if groupname.startswith(GROUP_PREFIX):
+ groupname = groupname[GROUP_PREFIX_LEN:]
+ if groupname in groups:
+ groups.remove(groupname)
+ self._updateUser(id, groups = groups)
+ userRemoveGroup = postonly(userRemoveGroup)
+
+
+ # #
+ # VARIOUS OPERATIONS #
+ # #
+
+ def __init__(self):
+ """
+ __init__(self) -> initialization method
+ We define it to prevend calling ancestor's __init__ methods.
+ """
+ pass
+
+
+ security.declarePrivate('_post_init')
+ def _post_init(self):
+ """
+ _post_init(self) => meant to be called when the
+ object is in the Zope tree
+ """
+ uf = GRUFFolder.GRUFUsers()
+ gf = GRUFFolder.GRUFGroups()
+ self._setObject('Users', uf)
+ self._setObject('Groups', gf)
+ self.id = "acl_users"
+
+ def manage_beforeDelete(self, item, container):
+ """
+ Special overloading for __allow_groups__ attribute
+ """
+ if item is self:
+ try:
+ del container.__allow_groups__
+ except:
+ pass
+
+ def manage_afterAdd(self, item, container):
+ """Same
+ """
+ if item is self:
+ container.__allow_groups__ = aq_base(self)
+
+ # #
+ # VARIOUS UTILITIES #
+ # #
+ # These methods shouldn't be used directly for most applications, #
+ # but they might be useful for some special processing. #
+ # #
+
+ security.declarePublic('getGroupPrefix')
+ def getGroupPrefix(self):
+ """ group prefix """
+ return GROUP_PREFIX
+
+ security.declarePrivate('getGRUFPhysicalRoot')
+ def getGRUFPhysicalRoot(self,):
+ # $$$ trick meant to be used within
+ # fake_getPhysicalRoot (see __init__)
+ return self.getPhysicalRoot()
+
+ security.declareProtected(Permissions.view, 'getGRUFId')
+ def getGRUFId(self,):
+ """
+ Alias to self.getId()
+ """
+ return self.getId()
+
+ security.declareProtected(Permissions.manage_users, "getUnwrappedUser")
+ def getUnwrappedUser(self, name):
+ """
+ getUnwrappedUser(self, name) => user object or None
+
+ This method is used to get a User object directly from the User's
+ folder acl_users, without wrapping it with group information.
+
+ This is useful for UserFolders that define additional User classes,
+ when you want to call specific methods on these user objects.
+
+ For example, LDAPUserFolder defines a 'getProperty' method that's
+ not inherited from the standard User object. You can, then, use
+ the getUnwrappedUser() to get the matching user and call this
+ method.
+ """
+ src_id = self.getUser(name).getUserSourceId()
+ return self.getUserSource(src_id).getUser(name)
+
+ security.declareProtected(Permissions.manage_users, "getUnwrappedGroup")
+ def getUnwrappedGroup(self, name):
+ """
+ getUnwrappedGroup(self, name) => user object or None
+
+ Same as getUnwrappedUser but for groups.
+ """
+ return self.Groups.acl_users.getUser(name)
+
+ # #
+ # AUTHENTICATION INTERFACE #
+ # #
+
+ security.declarePrivate("authenticate")
+ def authenticate(self, name, password, request):
+ """
+ Pass the request along to the underlying user-related UserFolder
+ object
+ THIS METHOD RETURNS A USER OBJECT OR NONE, as specified in the code
+ in AccessControl/User.py.
+ We also check for inituser in there.
+ """
+ # Emergency user checking stuff
+ emergency = self._emergency_user
+ if emergency and name == emergency.getUserName():
+ if emergency.authenticate(password, request):
+ return emergency
+ else:
+ return None
+
+ # Usual GRUF authentication
+ for src in self.listUserSources():
+ # XXX We can imagine putting a try/except here to "ignore"
+ # UF errors such as SQL or LDAP shutdown
+ u = src.authenticate(name, password, request)
+ if u:
+ return GRUFUser.GRUFUser(u, self, isGroup = 0, source_id = src.getUserSourceId()).__of__(self)
+
+ # No acl_users in the Users folder or no user authenticated
+ # => we refuse authentication
+ return None
+
+
+
+
+ # #
+ # GRUF'S GUTS :-) #
+ # #
+
+ security.declarePrivate("_doAddUser")
+ def _doAddUser(self, name, password, roles, domains, groups = (), **kw):
+ """
+ Create a new user. This should be implemented by subclasses to
+ do the actual adding of a user. The 'password' will be the
+ original input password, unencrypted. The implementation of this
+ method is responsible for performing any needed encryption.
+ """
+ prefix = GROUP_PREFIX
+
+ # Prepare groups
+ roles = list(roles)
+ gruf_groups = self.getGroupIds()
+ for group in groups:
+ if not group.startswith(prefix):
+ group = "%s%s" % (prefix, group, )
+ if not group in gruf_groups:
+ raise ValueError, "Invalid group: '%s'" % (group, )
+ roles.append(group)
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Really add users
+ return self.getDefaultUserSource()._doAddUser(
+ name,
+ password,
+ roles,
+ domains,
+ **kw)
+
+ security.declarePrivate("_doChangeUser")
+ def _doChangeUser(self, name, password, roles, domains, groups = None, **kw):
+ """
+ Modify an existing user. This should be implemented by subclasses
+ to make the actual changes to a user. The 'password' will be the
+ original input password, unencrypted. The implementation of this
+ method is responsible for performing any needed encryption.
+
+ A None password should not change it (well, we hope so)
+ """
+ # Get actual user name and id
+ usr = self.getUser(name)
+ if usr is None:
+ raise ValueError, "Invalid user: '%s'" % (name,)
+ id = usr.getRealId()
+
+ # Don't lose existing groups
+ if groups is None:
+ groups = usr.getGroups()
+
+ roles = list(roles)
+ groups = list(groups)
+
+ # Change groups affectation
+ cur_groups = self.getGroups()
+ given_roles = tuple(usr.getRoles()) + tuple(roles)
+ for group in groups:
+ if not group.startswith(GROUP_PREFIX, ):
+ group = "%s%s" % (GROUP_PREFIX, group, )
+ if not group in cur_groups and not group in given_roles:
+ roles.append(group)
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Change the user itself
+ src = usr.getUserSourceId()
+ Log(LOG_NOTICE, name, "Source:", src)
+ ret = self.getUserSource(src)._doChangeUser(
+ id, password, roles, domains, **kw)
+
+ # Invalidate user cache if necessary
+ usr.clearCachedGroupsAndRoles()
+ authenticated = getSecurityManager().getUser()
+ if id == authenticated.getId() and hasattr(authenticated, 'clearCachedGroupsAndRoles'):
+ authenticated.clearCachedGroupsAndRoles(self.getUserSource(src).getUser(id))
+
+ return ret
+
+ security.declarePrivate("_updateUser")
+ def _updateUser(self, id, password = None, roles = None, domains = None, groups = None):
+ """
+ _updateUser(self, id, password = None, roles = None, domains = None, groups = None)
+
+ This one should work for users AND groups.
+
+ Front-end to _doChangeUser, but with a better default value support.
+ We guarantee that None values will let the underlying UF keep the original ones.
+ This is not true for the password: some buggy UF implementation may not
+ handle None password correctly :-(
+ """
+ # Get the former values if necessary. Username must be valid !
+ usr = self.getUser(id)
+ if roles is None:
+ # Remove invalid roles and group names
+ roles = usr._original_roles
+ roles = filter(lambda x: not x.startswith(GROUP_PREFIX), roles)
+ roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared', ''), roles)
+ else:
+ # Check if roles are valid
+ roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared', ''), roles)
+ vr = self.userFolderGetRoles()
+ for r in roles:
+ if not r in vr:
+ raise ValueError, "Invalid or inexistant role: '%s'." % (r, )
+ if domains is None:
+ domains = usr._original_domains
+ if groups is None:
+ groups = usr.getGroups(no_recurse = 1)
+ else:
+ # Check if given groups are valid
+ glist = self.getGroupNames()
+ glist.extend(map(lambda x: "%s%s" % (GROUP_PREFIX, x), glist))
+ for g in groups:
+ if not g in glist:
+ raise ValueError, "Invalid group: '%s'" % (g, )
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Change the user
+ return self._doChangeUser(id, password, roles, domains, groups)
+
+ security.declarePrivate("_doDelUsers")
+ def _doDelUsers(self, names):
+ """
+ Delete one or more users. This should be implemented by subclasses
+ to do the actual deleting of users.
+ This won't delete groups !
+ """
+ # Collect information about user sources
+ sources = {}
+ for name in names:
+ usr = self.getUser(name, __include_groups__ = 0)
+ if not usr:
+ continue # Ignore invalid user names
+ src = usr.getUserSourceId()
+ if not sources.has_key(src):
+ sources[src] = []
+ sources[src].append(name)
+ for src, names in sources.items():
+ self.getUserSource(src)._doDelUsers(names)
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+
+ # #
+ # Groups interface #
+ # #
+
+ security.declarePrivate("_doAddGroup")
+ def _doAddGroup(self, name, roles, groups = (), **kw):
+ """
+ Create a new group. Password will be randomly created, and domain will be None.
+ Supports nested groups.
+ """
+ # Prepare initial data
+ domains = ()
+ password = ""
+ if roles is None:
+ roles = []
+ if groups is None:
+ groups = []
+
+ for x in range(0, 10): # Password will be 10 chars long
+ password = "%s%s" % (password, random.choice(string.lowercase), )
+
+ # Compute roles
+ roles = list(roles)
+ prefix = GROUP_PREFIX
+ gruf_groups = self.getGroupIds()
+ for group in groups:
+ if not group.startswith(prefix):
+ group = "%s%s" % (prefix, group, )
+ if group == "%s%s" % (prefix, name, ):
+ raise ValueError, "Infinite recursion for group '%s'." % (group, )
+ if not group in gruf_groups:
+ raise ValueError, "Invalid group: '%s' (defined groups are %s)" % (group, gruf_groups)
+ roles.append(group)
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Actual creation
+ return self.Groups.acl_users._doAddUser(
+ name, password, roles, domains, **kw
+ )
+
+ security.declarePrivate("_doChangeGroup")
+ def _doChangeGroup(self, name, roles, groups = None, **kw):
+ """Modify an existing group."""
+ # Remove prefix if given
+ if name.startswith(self.getGroupPrefix()):
+ name = name[GROUP_PREFIX_LEN:]
+
+ # Check if group exists
+ grp = self.getGroup(name, prefixed = 0)
+ if grp is None:
+ raise ValueError, "Invalid group: '%s'" % (name,)
+
+ # Don't lose existing groups
+ if groups is None:
+ groups = grp.getGroups()
+
+ roles = list(roles or [])
+ groups = list(groups or [])
+
+ # Change groups affectation
+ cur_groups = self.getGroups()
+ given_roles = tuple(grp.getRoles()) + tuple(roles)
+ for group in groups:
+ if not group.startswith(GROUP_PREFIX, ):
+ group = "%s%s" % (GROUP_PREFIX, group, )
+ if group == "%s%s" % (GROUP_PREFIX, grp.id):
+ raise ValueError, "Cannot affect group '%s' to itself!" % (name, ) # Prevent direct inclusion of self
+ new_grp = self.getGroup(group)
+ if not new_grp:
+ raise ValueError, "Invalid or inexistant group: '%s'" % (group, )
+ if "%s%s" % (GROUP_PREFIX, grp.id) in new_grp.getGroups():
+ raise ValueError, "Cannot affect %s to group '%s' as it would lead to circular references." % (group, name, ) # Prevent indirect inclusion of self
+ if not group in cur_groups and not group in given_roles:
+ roles.append(group)
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Perform the change
+ domains = ""
+ password = ""
+ for x in range(0, 10): # Password will be 10 chars long
+ password = "%s%s" % (password, random.choice(string.lowercase), )
+ return self.Groups.acl_users._doChangeUser(name, password,
+ roles, domains, **kw)
+
+ security.declarePrivate("_updateGroup")
+ def _updateGroup(self, name, roles = None, groups = None):
+ """
+ _updateGroup(self, name, roles = None, groups = None)
+
+ Front-end to _doChangeUser, but with a better default value support.
+ We guarantee that None values will let the underlying UF keep the original ones.
+ This is not true for the password: some buggy UF implementation may not
+ handle None password correctly but we do not care for Groups.
+
+ group name can be prefixed or not
+ """
+ # Remove prefix if given
+ if name.startswith(self.getGroupPrefix()):
+ name = name[GROUP_PREFIX_LEN:]
+
+ # Get the former values if necessary. Username must be valid !
+ usr = self.getGroup(name, prefixed = 0)
+ if roles is None:
+ # Remove invalid roles and group names
+ roles = usr._original_roles
+ roles = filter(lambda x: not x.startswith(GROUP_PREFIX), roles)
+ roles = filter(lambda x: x not in ('Anonymous', 'Authenticated', 'Shared'), roles)
+ if groups is None:
+ groups = usr.getGroups(no_recurse = 1)
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Change the user
+ return self._doChangeGroup(name, roles, groups)
+
+
+ security.declarePrivate("_doDelGroup")
+ def _doDelGroup(self, name):
+ """Delete one user."""
+ # Remove prefix if given
+ if name.startswith(self.getGroupPrefix()):
+ name = name[GROUP_PREFIX_LEN:]
+
+ # Reset the users overview batch
+ self._v_batch_users = []
+
+ # Delete it
+ return self.Groups.acl_users._doDelUsers([name])
+
+ security.declarePrivate("_doDelGroups")
+ def _doDelGroups(self, names):
+ """Delete one or more users."""
+ for group in names:
+ if not self.getGroupByName(group, None):
+ continue # Ignore invalid groups
+ self._doDelGroup(group)
+
+
+
+
+ # #
+ # Pretty Management form methods #
+ # #
+
+
+ security.declarePublic('getGRUFVersion')
+ def getGRUFVersion(self,):
+ """
+ getGRUFVersion(self,) => Return human-readable GRUF version as a string.
+ """
+ rev_date = "$Date: 2007-04-01 17:13:44 +0200 (dim, 01 avr 2007) $"[7:-2]
+ return "%s / Revised %s" % (version__, rev_date)
+
+
+ reset_entry = "__None__" # Special entry used for reset
+
+ security.declareProtected(Permissions.manage_users, "changeUser")
+ def changeUser(self, user, groups = [], roles = [], REQUEST = {}, ):
+ """
+ changeUser(self, user, groups = [], roles = [], REQUEST = {}, ) => used in ZMI
+ """
+ obj = self.getUser(user)
+ if obj.isGroup():
+ self._updateGroup(name = user, groups = groups, roles = roles, )
+ else:
+ self._updateUser(id = user, groups = groups, roles = roles, )
+
+
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + "/" + obj.getId() + "/manage_workspace?FORCE_USER=1")
+ changeUser = postonly(changeUser)
+
+ security.declareProtected(Permissions.manage_users, "deleteUser")
+ def deleteUser(self, user, REQUEST = {}, ):
+ """
+ deleteUser(self, user, REQUEST = {}, ) => used in ZMI
+ """
+ pass
+ deleteUser = postonly(deleteUser)
+
+ security.declareProtected(Permissions.manage_users, "changeOrCreateUsers")
+ def changeOrCreateUsers(self, users = [], groups = [], roles = [], new_users = [], default_password = '', REQUEST = {}, ):
+ """
+ changeOrCreateUsers => affect roles & groups to users and/or create new users
+
+ All parameters are strings or lists (NOT tuples !).
+ NO CHECKING IS DONE. This is an utility method, it's not part of the official API.
+ """
+ # Manage roles / groups deletion
+ del_roles = 0
+ del_groups = 0
+ if self.reset_entry in roles:
+ roles.remove(self.reset_entry)
+ del_roles = 1
+ if self.reset_entry in groups:
+ groups.remove(self.reset_entry)
+ del_groups = 1
+ if not roles and not del_roles:
+ roles = None # None instead of [] to avoid deletion
+ add_roles = []
+ else:
+ add_roles = roles
+ if not groups and not del_groups:
+ groups = None
+ add_groups = []
+ else:
+ add_groups = groups
+
+ # Passwords management
+ passwords_list = []
+
+ # Create brand new users
+ for new in new_users:
+ # Strip name
+ name = string.strip(new)
+ if not name:
+ continue
+
+ # Avoid erasing former users
+ if name in map(lambda x: x.getId(), self.getUsers()):
+ continue
+
+ # Use default password or generate a random one
+ if default_password:
+ password = default_password
+ else:
+ password = ""
+ for x in range(0, 8): # Password will be 8 chars long
+ password = "%s%s" % (password, random.choice("ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"), )
+ self._doAddUser(name, password, add_roles, (), add_groups, )
+
+ # Store the newly created password
+ passwords_list.append({'name':name, 'password':password})
+
+ # Update existing users
+ for user in users:
+ self._updateUser(id = user, groups = groups, roles = roles, )
+
+ # Web request
+ if REQUEST.has_key('RESPONSE'):
+ # Redirect if no users have been created
+ if not passwords_list:
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_users")
+
+ # Show passwords form
+ else:
+ REQUEST.set('USER_PASSWORDS', passwords_list)
+ return self.manage_newusers(None, self)
+
+ # Simply return the list of created passwords
+ return passwords_list
+ changeOrCreateUsers = postonly(changeOrCreateUsers)
+
+ security.declareProtected(Permissions.manage_users, "deleteUsers")
+ def deleteUsers(self, users = [], REQUEST = {}):
+ """
+ deleteUsers => explicit
+
+ All parameters are strings. NO CHECKING IS DONE. This is an utility method !
+ """
+ # Delete them
+ self._doDelUsers(users, )
+
+ # Redirect
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_users")
+ deleteUsers = postonly(deleteUsers)
+
+ security.declareProtected(Permissions.manage_users, "changeOrCreateGroups")
+ def changeOrCreateGroups(self, groups = [], roles = [], nested_groups = [], new_groups = [], REQUEST = {}, ):
+ """
+ changeOrCreateGroups => affect roles to groups and/or create new groups
+
+ All parameters are strings. NO CHECKING IS DONE. This is an utility method !
+ """
+ # Manage roles / groups deletion
+ del_roles = 0
+ del_groups = 0
+ if self.reset_entry in roles:
+ roles.remove(self.reset_entry)
+ del_roles = 1
+ if self.reset_entry in nested_groups:
+ nested_groups.remove(self.reset_entry)
+ del_groups = 1
+ if not roles and not del_roles:
+ roles = None # None instead of [] to avoid deletion
+ add_roles = []
+ else:
+ add_roles = roles
+ if not nested_groups and not del_groups:
+ nested_groups = None
+ add_groups = []
+ else:
+ add_groups = nested_groups
+
+ # Create brand new groups
+ for new in new_groups:
+ name = string.strip(new)
+ if not name:
+ continue
+ self._doAddGroup(name, roles, groups = add_groups)
+
+ # Update existing groups
+ for group in groups:
+ self._updateGroup(group, roles = roles, groups = nested_groups)
+
+ # Redirect
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_groups")
+ changeOrCreateGroups = postonly(changeOrCreateGroups)
+
+ security.declareProtected(Permissions.manage_users, "deleteGroups")
+ def deleteGroups(self, groups = [], REQUEST = {}):
+ """
+ deleteGroups => explicit
+
+ All parameters are strings. NO CHECKING IS DONE. This is an utility method !
+ """
+ # Delete groups
+ for group in groups:
+ self._doDelGroup(group, )
+
+ # Redirect
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + "/manage_groups")
+ deleteGroups = postonly(deleteGroups)
+
+ # #
+ # Local Roles Acquisition Blocking #
+ # Those two methods perform their own security check. #
+ # #
+
+ security.declarePublic("acquireLocalRoles")
+ def acquireLocalRoles(self, folder, status, REQUEST=None):
+ """
+ Enable or disable local role acquisition on the specified folder.
+ If status is true, it will enable, else it will disable.
+ Note that the user _must_ have the change_permissions permission on the
+ folder to allow changes on it.
+ If you want to use this code from a product, please use _acquireLocalRoles()
+ instead: this private method won't check security on the destination folder.
+ It's usually a bad idea to use _acquireLocalRoles() directly in your product,
+ but, well, after all, you do what you want ! :^)
+ """
+ # Perform security check on destination folder
+ if not getSecurityManager().checkPermission(Permissions.change_permissions, folder):
+ raise Unauthorized(name = "acquireLocalRoles")
+
+ return self._acquireLocalRoles(folder, status)
+ acquireLocalRoles = postonly(acquireLocalRoles)
+
+ def _acquireLocalRoles(self, folder, status):
+ """Same as _acquireLocalRoles() but won't perform security check on the folder.
+ """
+ # Set the variable (or unset it if it's defined)
+ if not status:
+ folder.__ac_local_roles_block__ = 1
+ else:
+ if getattr(folder, '__ac_local_roles_block__', None):
+ folder.__ac_local_roles_block__ = None
+
+
+ security.declarePublic("isLocalRoleAcquired")
+ def isLocalRoleAcquired(self, folder):
+ """Return true if the specified folder allows local role acquisition.
+ """
+ if getattr(folder, '__ac_local_roles_block__', None):
+ return 0
+ return 1
+
+
+ # #
+ # Security audit and info methods #
+ # #
+
+
+ # This method normally has NOT to be public ! It is because of a CMF inconsistancy.
+ # folder_localrole_form is accessible to users who have the manage_properties permissions
+ # (according to portal_types/Folder/Actions information). This is silly !
+ # folder_localrole_form should be, in CMF, accessible only to those who have the
+ # manage_users permissions instead of manage_properties permissions.
+ # This is yet another one CMF bug we have to care about.
+ # To deal with that in Plone2.1, we check for a particular permission on the destination
+ # object _inside_ the method.
+ security.declarePublic("getLocalRolesForDisplay")
+ def getLocalRolesForDisplay(self, object):
+ """This is used for plone's local roles display
+ This method returns a tuple (massagedUsername, roles, userType, actualUserName).
+ This method is protected by the 'Manage properties' permission. We may
+ change that if it's too permissive..."""
+ # Perform security check on destination object
+ if not getSecurityManager().checkPermission(Permissions.manage_properties, object):
+ raise Unauthorized(name = "getLocalRolesForDisplay")
+
+ return self._getLocalRolesForDisplay(object)
+
+ def _getLocalRolesForDisplay(self, object):
+ """This is used for plone's local roles display
+ This method returns a tuple (massagedUsername, roles, userType, actualUserName)"""
+ result = []
+ local_roles = object.get_local_roles()
+ prefix = self.getGroupPrefix()
+ for one_user in local_roles:
+ massagedUsername = username = one_user[0]
+ roles = one_user[1]
+ userType = 'user'
+ if prefix:
+ if self.getGroupById(username) is not None:
+ massagedUsername = username[len(prefix):]
+ userType = 'group'
+ else:
+ userType = 'unknown'
+ result.append((massagedUsername, roles, userType, username))
+ return tuple(result)
+
+
+ security.declarePublic("getAllLocalRoles")
+ def getAllLocalRoles(self, object):
+ """getAllLocalRoles(self, object): return a dictionnary {useratom_id: roles} of local
+ roles defined AND herited at a certain point. This will handle lr-blocking
+ as well.
+ """
+ # Perform security check on destination object
+ if not getSecurityManager().checkPermission(Permissions.change_permissions, object):
+ raise Unauthorized(name = "getAllLocalRoles")
+
+ return self._getAllLocalRoles(object)
+
+
+ def _getAllLocalRoles(self, object):
+ """getAllLocalRoles(self, object): return a dictionnary {useratom_id: roles} of local
+ roles defined AND herited at a certain point. This will handle lr-blocking
+ as well.
+ """
+ # Modified from AccessControl.User.getRolesInContext().
+ merged = {}
+ object = getattr(object, 'aq_inner', object)
+ while 1:
+ if hasattr(object, '__ac_local_roles__'):
+ dict = object.__ac_local_roles__ or {}
+ if callable(dict): dict = dict()
+ for k, v in dict.items():
+ if not merged.has_key(k):
+ merged[k] = {}
+ for role in v:
+ merged[k][role] = 1
+ if not self.isLocalRoleAcquired(object):
+ break
+ if hasattr(object, 'aq_parent'):
+ object=object.aq_parent
+ object=getattr(object, 'aq_inner', object)
+ continue
+ if hasattr(object, 'im_self'):
+ object=object.im_self
+ object=getattr(object, 'aq_inner', object)
+ continue
+ break
+ for key, value in merged.items():
+ merged[key] = value.keys()
+ return merged
+
+
+
+ # Plone-specific security matrix computing method.
+ security.declarePublic("getPloneSecurityMatrix")
+ def getPloneSecurityMatrix(self, object):
+ """getPloneSecurityMatrix(self, object): return a list of dicts of the current object
+ and all its parents. The list is sorted with portal object first.
+ Each dict has the following structure:
+ {
+ depth: (0 for portal root, 1 for 1st-level folders and so on),
+ id:
+ title:
+ icon:
+ absolute_url:
+ security_permission: true if current user can change security on this object
+ state: (workflow state)
+ acquired_local_roles: 0 if local role blocking is enabled for this folder
+ roles: {
+ 'role1': {
+ 'all_local_roles': [r1, r2, r3, ] (all defined local roles, including parent ones)
+ 'defined_local_roles': [r3, ] (local-defined only local roles)
+ 'permissions': ['Access contents information', 'Modify portal content', ] (only a subset)
+ 'same_permissions': true if same permissions as the parent
+ 'same_all_local_roles': true if all_local_roles is the same as the parent
+ 'same_defined_local_roles': true if defined_local_roles is the same as the parent
+ },
+ 'role2': {...},
+ },
+ }
+ """
+ # Perform security check on destination object
+ if not getSecurityManager().checkPermission(Permissions.access_contents_information, object):
+ raise Unauthorized(name = "getPloneSecurityMatrix")
+
+ # Basic inits
+ mt = self.portal_membership
+
+ # Fetch all possible roles in the portal
+ all_roles = ['Anonymous'] + mt.getPortalRoles()
+
+ # Fetch parent folders list until the portal
+ all_objects = []
+ cur_object = object
+ while 1:
+ if not getSecurityManager().checkPermission(Permissions.access_contents_information, cur_object):
+ raise Unauthorized(name = "getPloneSecurityMatrix")
+ all_objects.append(cur_object)
+ if cur_object.meta_type == "Plone Site":
+ break
+ cur_object = object.aq_parent
+ all_objects.reverse()
+
+ # Scan those folders to get all the required information about them
+ ret = []
+ previous = None
+ count = 0
+ for obj in all_objects:
+ # Basic information
+ current = {
+ "depth": count,
+ "id": obj.getId(),
+ "title": obj.Title(),
+ "icon": obj.getIcon(),
+ "absolute_url": obj.absolute_url(),
+ "security_permission": getSecurityManager().checkPermission(Permissions.change_permissions, obj),
+ "acquired_local_roles": self.isLocalRoleAcquired(obj),
+ "roles": {},
+ "state": "XXX TODO XXX", # XXX TODO
+ }
+ count += 1
+
+ # Workflow state
+ # XXX TODO
+
+ # Roles
+ all_local_roles = {}
+ local_roles = self._getAllLocalRoles(obj)
+ for user, roles in self._getAllLocalRoles(obj).items():
+ for role in roles:
+ if not all_local_roles.has_key(role):
+ all_local_roles[role] = {}
+ all_local_roles[role][user] = 1
+ defined_local_roles = {}
+ if hasattr(obj.aq_base, 'get_local_roles'):
+ for user, roles in obj.get_local_roles():
+ for role in roles:
+ if not defined_local_roles.has_key(role):
+ defined_local_roles[role] = {}
+ defined_local_roles[role][user] = 1
+
+ for role in all_roles:
+ all = all_local_roles.get(role, {}).keys()
+ defined = defined_local_roles.get(role, {}).keys()
+ all.sort()
+ defined.sort()
+ same_all_local_roles = 0
+ same_defined_local_roles = 0
+ if previous:
+ if previous['roles'][role]['all_local_roles'] == all:
+ same_all_local_roles = 1
+ if previous['roles'][role]['defined_local_roles'] == defined:
+ same_defined_local_roles = 1
+
+ current['roles'][role] = {
+ "all_local_roles": all,
+ "defined_local_roles": defined,
+ "same_all_local_roles": same_all_local_roles,
+ "same_defined_local_roles": same_defined_local_roles,
+ "permissions": [], # XXX TODO
+ }
+
+ ret.append(current)
+ previous = current
+
+ return ret
+
+
+ security.declareProtected(Permissions.manage_users, "computeSecuritySettings")
+ def computeSecuritySettings(self, folders, actors, permissions, cache = {}):
+ """
+ computeSecuritySettings(self, folders, actors, permissions, cache = {}) => return a structure that is suitable for security audit Page Template.
+
+ - folders is the structure returned by getSiteTree()
+ - actors is the structure returned by listUsersAndRoles()
+ - permissions is ((id: permission), (id: permission), ...)
+ - cache is passed along requests to make computing faster
+ """
+ # Scan folders and actors to get the relevant information
+ usr_cache = {}
+ for id, depth, path in folders:
+ folder = self.unrestrictedTraverse(path)
+ for kind, actor, display, handle, html in actors:
+ if kind in ("user", "group"):
+ # Init structure
+ if not cache.has_key(path):
+ cache[path] = {(kind, actor): {}}
+ elif not cache[path].has_key((kind, actor)):
+ cache[path][(kind, actor)] = {}
+ else:
+ cache[path][(kind, actor)] = {}
+
+ # Split kind into groups and get individual role information
+ perm_keys = []
+ usr = usr_cache.get(actor)
+ if not usr:
+ usr = self.getUser(actor)
+ usr_cache[actor] = usr
+ roles = usr.getRolesInContext(folder,)
+ for role in roles:
+ for perm_key in self.computeSetting(path, folder, role, permissions, cache).keys():
+ cache[path][(kind, actor)][perm_key] = 1
+
+ else:
+ # Get role information
+ self.computeSetting(path, folder, actor, permissions, cache)
+
+ # Return the computed cache
+ return cache
+
+
+ security.declareProtected(Permissions.manage_users, "computeSetting")
+ def computeSetting(self, path, folder, actor, permissions, cache):
+ """
+ computeSetting(......) => used by computeSecuritySettings to populate the cache for ROLES
+ """
+ # Avoid doing things twice
+ kind = "role"
+ if cache.get(path, {}).get((kind, actor), None) is not None:
+ return cache[path][(kind, actor)]
+
+ # Initilize cache structure
+ if not cache.has_key(path):
+ cache[path] = {(kind, actor): {}}
+ elif not cache[path].has_key((kind, actor)):
+ cache[path][(kind, actor)] = {}
+
+ # Analyze permission settings
+ ps = folder.permission_settings()
+ for perm_key, permission in permissions:
+ # Check acquisition of permission setting.
+ can = 0
+ acquired = 0
+ for p in ps:
+ if p['name'] == permission:
+ acquired = not not p['acquire']
+
+ # If acquired, call the parent recursively
+ if acquired:
+ parent = folder.aq_parent.getPhysicalPath()
+ perms = self.computeSetting(parent, self.unrestrictedTraverse(parent), actor, permissions, cache)
+ can = perms.get(perm_key, None)
+
+ # Else, check permission here
+ else:
+ for p in folder.rolesOfPermission(permission):
+ if p['name'] == "Anonymous":
+ # If anonymous is allowed, then everyone is allowed
+ if p['selected']:
+ can = 1
+ break
+ if p['name'] == actor:
+ if p['selected']:
+ can = 1
+ break
+
+ # Extend the data structure according to 'can' setting
+ if can:
+ cache[path][(kind, actor)][perm_key] = 1
+
+ return cache[path][(kind, actor)]
+
+
+ security.declarePrivate('_getNextHandle')
+ def _getNextHandle(self, index):
+ """
+ _getNextHandle(self, index) => utility function to
+ get an unique handle for each legend item.
+ """
+ return "%02d" % index
+
+
+ security.declareProtected(Permissions.manage_users, "listUsersAndRoles")
+ def listUsersAndRoles(self,):
+ """
+ listUsersAndRoles(self,) => list of tuples
+
+ This method is used by the Security Audit page.
+ XXX HAS TO BE OPTIMIZED
+ """
+ request = self.REQUEST
+ display_roles = request.get('display_roles', 0)
+ display_groups = request.get('display_groups', 0)
+ display_users = request.get('display_users', 0)
+
+ role_index = 0
+ user_index = 0
+ group_index = 0
+ ret = []
+
+ # Collect roles
+ if display_roles:
+ for r in self.aq_parent.valid_roles():
+ handle = "R%02d" % role_index
+ role_index += 1
+ ret.append(('role', r, r, handle, r))
+
+ # Collect users
+ if display_users:
+ for u in map(lambda x: x.getId(), self.getPureUsers()):
+ obj = self.getUser(u)
+ html = obj.asHTML()
+ handle = "U%02d" % user_index
+ user_index += 1
+ ret.append(('user', u, u, handle, html))
+
+ if display_groups:
+ for u in self.getGroupNames():
+ obj = self.getUser(u)
+ handle = "G%02d" % group_index
+ html = obj.asHTML()
+ group_index += 1
+ ret.append(('group', u, obj.getUserNameWithoutGroupPrefix(), handle, html))
+
+ # Return list
+ return ret
+
+ security.declareProtected(Permissions.manage_users, "getSiteTree")
+ def getSiteTree(self, obj=None, depth=0):
+ """
+ getSiteTree(self, obj=None, depth=0) => special structure
+
+ This is used by the security audit page
+ """
+ ret = []
+ if not obj:
+ if depth==0:
+ obj = self.aq_parent
+ else:
+ return ret
+
+ ret.append([obj.getId(), depth, string.join(obj.getPhysicalPath(), '/')])
+ for sub in obj.objectValues():
+ try:
+ # Ignore user folders
+ if sub.getId() in ('acl_users', ):
+ continue
+
+ # Ignore portal_* stuff
+ if sub.getId()[:len('portal_')] == 'portal_':
+ continue
+
+ if sub.isPrincipiaFolderish:
+ ret.extend(self.getSiteTree(sub, depth + 1))
+
+ except:
+ # We ignore exceptions
+ pass
+
+ return ret
+
+ security.declareProtected(Permissions.manage_users, "listAuditPermissions")
+ def listAuditPermissions(self,):
+ """
+ listAuditPermissions(self,) => return a list of eligible permissions
+ """
+ ps = self.permission_settings()
+ return map(lambda p: p['name'], ps)
+
+ security.declareProtected(Permissions.manage_users, "getDefaultPermissions")
+ def getDefaultPermissions(self,):
+ """
+ getDefaultPermissions(self,) => return default R & W permissions for security audit.
+ """
+ # If there's a Plone site in the above folder, use plonish permissions
+ hasPlone = 0
+ p = self.aq_parent
+ if p.meta_type == "CMF Site":
+ hasPlone = 1
+ else:
+ for obj in p.objectValues():
+ if obj.meta_type == "CMF Site":
+ hasPlone = 1
+ break
+
+ if hasPlone:
+ return {'R': 'View',
+ 'W': 'Modify portal content',
+ }
+ else:
+ return {'R': 'View',
+ 'W': 'Change Images and Files',
+ }
+
+
+ # #
+ # Users/Groups tree view #
+ # (ZMI only) #
+ # #
+
+
+ security.declarePrivate('getTreeInfo')
+ def getTreeInfo(self, usr, dict = {}):
+ "utility method"
+ # Prevend infinite recursions
+ name = usr.getUserName()
+ if dict.has_key(name):
+ return
+ dict[name] = {}
+
+ # Properties
+ noprefix = usr.getUserNameWithoutGroupPrefix()
+ is_group = usr.isGroup()
+ if usr.isGroup():
+ icon = string.join(self.getPhysicalPath(), '/') + '/img_group'
+## icon = self.absolute_url() + '/img_group'
+ else:
+ icon = ' img_user'
+## icon = self.absolute_url() + '/img_user'
+
+ # Subobjects
+ belongs_to = []
+ for grp in usr.getGroups(no_recurse = 1):
+ belongs_to.append(grp)
+ self.getTreeInfo(self.getGroup(grp))
+
+ # Append (and return) structure
+ dict[name] = {
+ "name": noprefix,
+ "is_group": is_group,
+ "icon": icon,
+ "belongs_to": belongs_to,
+ }
+ return dict
+
+
+ security.declarePrivate("tpValues")
+ def tpValues(self):
+ # Avoid returning HUUUUUUGE lists
+ # Use the cache at first
+ if self._v_no_tree and self._v_cache_no_tree > time.time():
+ return [] # Do not use the tree
+
+ # XXX - I DISABLE THE TREE BY NOW (Pb. with icon URL)
+ return []
+
+ # Then, use a simple computation to determine opportunity to use the tree or not
+ ngroups = len(self.getGroupNames())
+ if ngroups > MAX_TREE_USERS_AND_GROUPS:
+ self._v_no_tree = 1
+ self._v_cache_no_tree = time.time() + TREE_CACHE_TIME
+ return []
+ nusers = len(self.getUsers())
+ if ngroups + nusers > MAX_TREE_USERS_AND_GROUPS:
+ meth_list = self.getGroups
+ else:
+ meth_list = self.getUsers
+ self._v_no_tree = 0
+
+ # Get top-level user and groups list
+ tree_dict = {}
+ top_level_names = []
+ top_level = []
+ for usr in meth_list():
+ self.getTreeInfo(usr, tree_dict)
+ if not usr.getGroups(no_recurse = 1):
+ top_level_names.append(usr.getUserName())
+ for id in top_level_names:
+ top_level.append(treeWrapper(id, tree_dict))
+
+ # Return this top-level list
+ top_level.sort(lambda x, y: cmp(x.sortId(), y.sortId()))
+ return top_level
+
+
+ def tpId(self,):
+ return self.getId()
+
+
+ # #
+ # Direct traversal to user or group info #
+ # #
+
+ def manage_workspace(self, REQUEST):
+ """
+ manage_workspace(self, REQUEST) => Overrided to allow direct user or group traversal
+ via the left tree view.
+ """
+ path = string.split(REQUEST.PATH_INFO, '/')[:-1]
+ userid = path[-1]
+
+ # Use individual usr/grp management screen (only if name is passed along the mgt URL)
+ if userid != "acl_users":
+ usr = self.getUserById(userid)
+ if usr:
+ REQUEST.set('username', userid)
+ REQUEST.set('MANAGE_TABS_NO_BANNER', '1') # Prevent use of the manage banner
+ return self.restrictedTraverse('manage_user')()
+
+ # Default management screen
+ return self.restrictedTraverse('manage_overview')()
+
+
+ # Tree caching information
+ _v_no_tree = 0
+ _v_cache_no_tree = 0
+ _v_cache_tree = (0, [])
+
+
+ def __bobo_traverse__(self, request, name):
+ """
+ Looks for the name of a user or a group.
+ This applies only if users list is not huge.
+ """
+ # Check if it's an attribute
+ if hasattr(self.aq_base, name, ):
+ return getattr(self, name)
+
+ # It's not an attribute, maybe it's a user/group
+ # (this feature is used for the tree)
+ if name.startswith('_'):
+ pass # Do not fetch users
+ elif name.startswith('manage_'):
+ pass # Do not fetch users
+ elif name in INVALID_USER_NAMES:
+ pass # Do not fetch users
+ else:
+ # Only try to get users is fetch_user is true.
+ # This is only for performance reasons.
+ # The following code block represent what we want to minimize
+ if self._v_cache_tree[0] < time.time():
+ un = map(lambda x: x.getId(), self.getUsers()) # This is the cost we want to avoid
+ self._v_cache_tree = (time.time() + TREE_CACHE_TIME, un, )
+ else:
+ un = self._v_cache_tree[1]
+
+ # Get the user if we can
+ if name in un:
+ self._v_no_tree = 0
+ return self
+
+ # Force getting the user if we must
+ if request.get("FORCE_USER"):
+ self._v_no_tree = 0
+ return self
+
+ # This will raise if it's not possible to acquire 'name'
+ return getattr(self, name, )
+
+
+
+ # #
+ # USERS / GROUPS BATCHING (ZMI SCREENS) #
+ # #
+
+ _v_batch_users = []
+
+ security.declareProtected(Permissions.view_management_screens, "listUsersBatches")
+ def listUsersBatches(self,):
+ """
+ listUsersBatches(self,) => return a list of (start, end) tuples.
+ Return None if batching is not necessary
+ """
+ # Time-consuming stuff !
+ un = map(lambda x: x.getId(), self.getPureUsers())
+ if len(un) <= MAX_USERS_PER_PAGE:
+ return None
+ un.sort()
+
+ # Split this list into small groups if necessary
+ ret = []
+ idx = 0
+ l_un = len(un)
+ nbatches = int(math.ceil(l_un / float(MAX_USERS_PER_PAGE)))
+ for idx in range(0, nbatches):
+ first = idx * MAX_USERS_PER_PAGE
+ last = first + MAX_USERS_PER_PAGE - 1
+ if last >= l_un:
+ last = l_un - 1
+ # Append a tuple (not dict) to avoid too much memory consumption
+ ret.append((first, last, un[first], un[last]))
+
+ # Cache & return it
+ self._v_batch_users = un
+ return ret
+
+ security.declareProtected(Permissions.view_management_screens, "listUsersBatchTable")
+ def listUsersBatchTable(self,):
+ """
+ listUsersBatchTable(self,) => Same a mgt screens but divided into sublists to
+ present them into 5 columns.
+ XXX have to merge this w/getUsersBatch to make it in one single pass
+ """
+ # Iterate
+ ret = []
+ idx = 0
+ current = []
+ for rec in (self.listUsersBatches() or []):
+ if not idx % 5:
+ if current:
+ ret.append(current)
+ current = []
+ current.append(rec)
+ idx += 1
+
+ if current:
+ ret.append(current)
+
+ return ret
+
+ security.declareProtected(Permissions.view_management_screens, "getUsersBatch")
+ def getUsersBatch(self, start):
+ """
+ getUsersBatch(self, start) => user list
+ """
+ # Rebuild the list if necessary
+ if not self._v_batch_users:
+ un = map(lambda x: x.getId(), self.getPureUsers())
+ self._v_batch_users = un
+
+ # Return the batch
+ end = start + MAX_USERS_PER_PAGE
+ ids = self._v_batch_users[start:end]
+ ret = []
+ for id in ids:
+ usr = self.getUser(id)
+ if usr: # Prevent adding invalid users
+ ret.append(usr)
+ return ret
+
+
+ # #
+ # Multiple sources management #
+ # #
+
+ # Arrows
+ img_up_arrow = ImageFile.ImageFile('www/up_arrow.gif', globals())
+ img_down_arrow = ImageFile.ImageFile('www/down_arrow.gif', globals())
+ img_up_arrow_grey = ImageFile.ImageFile('www/up_arrow_grey.gif', globals())
+ img_down_arrow_grey = ImageFile.ImageFile('www/down_arrow_grey.gif', globals())
+
+ security.declareProtected(Permissions.manage_users, "toggleSource")
+ def toggleSource(self, src_id, REQUEST = {}):
+ """
+ toggleSource(self, src_id, REQUEST = {}) => toggle enabled/disabled source
+ """
+ # Find the source
+ ids = self.objectIds('GRUFUsers')
+ if not src_id in ids:
+ raise ValueError, "Invalid source: '%s' (%s)" % (src_id, ids)
+ src = getattr(self, src_id)
+ if src.enabled:
+ src.disableSource()
+ else:
+ src.enableSource()
+
+ # Redirect where we want to
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
+
+
+ security.declareProtected(Permissions.manage_users, "listUserSources")
+ def listUserSources(self, ):
+ """
+ listUserSources(self, ) => Return a list of userfolder objects
+ Only return VALID (ie containing an acl_users) user sources if all is None
+ XXX HAS TO BE OPTIMIZED VERY MUCH!
+ We add a check in debug mode to ensure that invalid sources won't be added
+ to the list.
+ This method return only _enabled_ user sources.
+ """
+ ret = []
+ dret = {}
+ if DEBUG_MODE:
+ for src in self.objectValues(['GRUFUsers']):
+ if not src.enabled:
+ continue
+ if 'acl_users' in src.objectIds():
+ if getattr(aq_base(src.acl_users), 'authenticate', None): # Additional check in debug mode
+ dret[src.id] = src.acl_users # we cannot use restrictedTraverse here because
+ # of infinite recursion issues.
+ else:
+ for src in self.objectValues(['GRUFUsers']):
+ if not src.enabled:
+ continue
+ if not 'acl_users' in src.objectIds():
+ continue
+ dret[src.id] = src.acl_users
+ ret = dret.items()
+ ret.sort()
+ return [ src[1] for src in ret ]
+
+ security.declareProtected(Permissions.manage_users, "listUserSourceFolders")
+ def listUserSourceFolders(self, ):
+ """
+ listUserSources(self, ) => Return a list of GRUFUsers objects
+ """
+ ret = []
+ for src in self.objectValues(['GRUFUsers']):
+ ret.append(src)
+ ret.sort(lambda x,y: cmp(x.id, y.id))
+ return ret
+
+ security.declarePrivate("getUserSource")
+ def getUserSource(self, id):
+ """
+ getUserSource(self, id) => GRUFUsers.acl_users object.
+ Raises if no acl_users available
+ """
+ return getattr(self, id).acl_users
+
+ security.declarePrivate("getUserSourceFolder")
+ def getUserSourceFolder(self, id):
+ """
+ getUserSourceFolder(self, id) => GRUFUsers object
+ """
+ return getattr(self, id)
+
+ security.declareProtected(Permissions.manage_users, "addUserSource")
+ def addUserSource(self, factory_uri, REQUEST = {}, *args, **kw):
+ """
+ addUserSource(self, factory_uri, REQUEST = {}, *args, **kw) => redirect
+ Adds the specified user folder
+ """
+ # Get the initial Users id
+ ids = self.objectIds('GRUFUsers')
+ if ids:
+ ids.sort()
+ if ids == ['Users',]:
+ last = 0
+ else:
+ last = int(ids[-1][-2:])
+ next_id = "Users%02d" % (last + 1, )
+ else:
+ next_id = "Users"
+
+ # Add the GRUFFolder object
+ uf = GRUFFolder.GRUFUsers(id = next_id)
+ self._setObject(next_id, uf)
+
+## # If we use ldap, tag it
+## if string.find(factory_uri.lower(), "ldap") > -1:
+## self._haveLDAPUF += 1
+
+ # Add its underlying UserFolder
+ # If we're called TTW, uses a redirect else tries to call the UF factory directly
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect("%s/%s/%s" % (self.absolute_url(), next_id, factory_uri))
+ return getattr(self, next_id).unrestrictedTraverse(factory_uri)(*args, **kw)
+ addUserSource = postonly(addUserSource)
+
+ security.declareProtected(Permissions.manage_users, "deleteUserSource")
+ def deleteUserSource(self, id = None, REQUEST = {}):
+ """
+ deleteUserSource(self, id = None, REQUEST = {}) => Delete the specified user source
+ """
+ # Check the source id
+ if type(id) != type('s'):
+ raise ValueError, "You must choose a valid source to delete and confirm it."
+
+ # Delete it
+ self.manage_delObjects([id,])
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
+ deleteUserSource = postonly(deleteUserSource)
+
+ security.declareProtected(Permissions.manage_users, "getDefaultUserSource")
+ def getDefaultUserSource(self,):
+ """
+ getDefaultUserSource(self,) => acl_users object
+ Return default user source for user writing.
+ XXX By now, the FIRST source is the default one. This may change in the future.
+ """
+ lst = self.listUserSources()
+ if not lst:
+ raise RuntimeError, "No valid User Source to add users in."
+ return lst[0]
+
+
+ security.declareProtected(Permissions.manage_users, "listAvailableUserSources")
+ def listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1):
+ """
+ listAvailableUserSources(self, filter_permissions = 1, filter_classes = 1) => tuples (name, factory_uri)
+ List UserFolder replacement candidates.
+
+ - if filter_classes is true, return only ones which have a base UserFolder class
+ - if filter_permissions, return only types the user has rights to add
+ """
+ ret = []
+
+ # Fetch candidate types
+ user = getSecurityManager().getUser()
+ meta_types = []
+ if callable(self.all_meta_types):
+ all=self.all_meta_types()
+ else:
+ all=self.all_meta_types
+ for meta_type in all:
+ if filter_permissions and meta_type.has_key('permission'):
+ if user.has_permission(meta_type['permission'],self):
+ meta_types.append(meta_type)
+ else:
+ meta_types.append(meta_type)
+
+ # Keep only, if needed, BasicUserFolder-derived classes
+ for t in meta_types:
+ if t['name'] == self.meta_type:
+ continue # Do not keep GRUF ! ;-)
+
+ if filter_classes:
+ try:
+ if t.get('instance', None) and t['instance'].isAUserFolder:
+ ret.append((t['name'], t['action']))
+ continue
+ if t.get('instance', None) and class_utility.isBaseClass(AccessControl.User.BasicUserFolder, t['instance']):
+ ret.append((t['name'], t['action']))
+ continue
+ except AttributeError:
+ pass # We ignore 'invalid' instances (ie. that wouldn't define a __base__ attribute)
+ else:
+ ret.append((t['name'], t['action']))
+
+ return tuple(ret)
+
+ security.declareProtected(Permissions.manage_users, "moveUserSourceUp")
+ def moveUserSourceUp(self, id, REQUEST = {}):
+ """
+ moveUserSourceUp(self, id, REQUEST = {}) => used in management screens
+ try to get ids as consistant as possible
+ """
+ # List and sort sources and preliminary checks
+ ids = self.objectIds('GRUFUsers')
+ ids.sort()
+ if not ids or not id in ids:
+ raise ValueError, "Invalid User Source: '%s'" % (id,)
+
+ # Find indexes to swap
+ src_index = ids.index(id)
+ if src_index == 0:
+ raise ValueError, "Cannot move '%s' User Source up." % (id, )
+ dest_index = src_index - 1
+
+ # Find numbers to swap, fix them if they have more than 1 as offset
+ if ids[dest_index] == 'Users':
+ dest_num = 0
+ else:
+ dest_num = int(ids[dest_index][-2:])
+ src_num = dest_num + 1
+
+ # Get ids
+ src_id = id
+ if dest_num == 0:
+ dest_id = "Users"
+ else:
+ dest_id = "Users%02d" % (dest_num,)
+ tmp_id = "%s_" % (dest_id, )
+
+ # Perform the swap
+ self._renameUserSource(src_id, tmp_id)
+ self._renameUserSource(dest_id, src_id)
+ self._renameUserSource(tmp_id, dest_id)
+
+ # Return back to the forms
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
+ moveUserSourceUp = postonly(moveUserSourceUp)
+
+ security.declareProtected(Permissions.manage_users, "moveUserSourceDown")
+ def moveUserSourceDown(self, id, REQUEST = {}):
+ """
+ moveUserSourceDown(self, id, REQUEST = {}) => used in management screens
+ try to get ids as consistant as possible
+ """
+ # List and sort sources and preliminary checks
+ ids = self.objectIds('GRUFUsers')
+ ids.sort()
+ if not ids or not id in ids:
+ raise ValueError, "Invalid User Source: '%s'" % (id,)
+
+ # Find indexes to swap
+ src_index = ids.index(id)
+ if src_index == len(ids) - 1:
+ raise ValueError, "Cannot move '%s' User Source up." % (id, )
+ dest_index = src_index + 1
+
+ # Find numbers to swap, fix them if they have more than 1 as offset
+ if id == 'Users':
+ dest_num = 1
+ else:
+ dest_num = int(ids[dest_index][-2:])
+ src_num = dest_num - 1
+
+ # Get ids
+ src_id = id
+ if dest_num == 0:
+ dest_id = "Users"
+ else:
+ dest_id = "Users%02d" % (dest_num,)
+ tmp_id = "%s_" % (dest_id, )
+
+ # Perform the swap
+ self._renameUserSource(src_id, tmp_id)
+ self._renameUserSource(dest_id, src_id)
+ self._renameUserSource(tmp_id, dest_id)
+
+ # Return back to the forms
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_GRUFSources')
+ moveUserSourceDown = postonly(moveUserSourceDown)
+
+
+ security.declarePrivate('_renameUserSource')
+ def _renameUserSource(self, id, new_id, ):
+ """
+ Rename a particular sub-object.
+ Taken fro CopySupport.manage_renameObject() code, modified to disable verifications.
+ """
+ try: self._checkId(new_id)
+ except: raise CopyError, MessageDialog(
+ title='Invalid Id',
+ message=sys.exc_info()[1],
+ action ='manage_main')
+ ob=self._getOb(id)
+## if not ob.cb_isMoveable():
+## raise "Copy Error", eNotSupported % id
+## self._verifyObjectPaste(ob) # This is what we disable
+ try: ob._notifyOfCopyTo(self, op=1)
+ except: raise CopyError, MessageDialog(
+ title='Rename Error',
+ message=sys.exc_info()[1],
+ action ='manage_main')
+ self._delObject(id)
+ ob = aq_base(ob)
+ ob._setId(new_id)
+
+ # Note - because a rename always keeps the same context, we
+ # can just leave the ownership info unchanged.
+ self._setObject(new_id, ob, set_owner=0)
+
+
+ security.declareProtected(Permissions.manage_users, "replaceUserSource")
+ def replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw):
+ """
+ replaceUserSource(self, id = None, new_factory = None, REQUEST = {}, *args, **kw) => perform user source replacement
+
+ If new_factory is None, find it inside REQUEST (useful for ZMI screens)
+ """
+ # Check the source id
+ if type(id) != type('s'):
+ raise ValueError, "You must choose a valid source to replace and confirm it."
+
+ # Retreive factory if not explicitly passed
+ if not new_factory:
+ for record in REQUEST.get("source_rec", []):
+ if record['id'] == id:
+ new_factory = record['new_factory']
+ break
+ if not new_factory:
+ raise ValueError, "You must select a new User Folder type."
+
+ # Delete the former one
+ us = getattr(self, id)
+ if "acl_users" in us.objectIds():
+ us.manage_delObjects(['acl_users'])
+
+ ## If we use ldap, tag it
+ #if string.find(new_factory.lower(), "ldap") > -1:
+ # self._haveLDAPUF += 1
+
+ # Re-create the underlying UserFolder
+ # If we're called TTW, uses a redirect else tries to call the UF factory directly
+ if REQUEST.has_key('RESPONSE'):
+ return REQUEST.RESPONSE.redirect("%s/%s/%s" % (self.absolute_url(), id, new_factory))
+ return us.unrestrictedTraverse(new_factory)(*args, **kw) # XXX minor security pb ?
+ replaceUserSource = postonly(replaceUserSource)
+
+
+ security.declareProtected(Permissions.manage_users, "hasLDAPUserFolderSource")
+ def hasLDAPUserFolderSource(self, ):
+ """
+ hasLDAPUserFolderSource(self,) => boolean
+ Return true if a LUF source is instanciated.
+ """
+ for src in self.listUserSources():
+ if src.meta_type == "LDAPUserFolder":
+ return 1
+ return None
+
+
+ security.declareProtected(Permissions.manage_users, "updateLDAPUserFolderMapping")
+ def updateLDAPUserFolderMapping(self, REQUEST = None):
+ """
+ updateLDAPUserFolderMapping(self, REQUEST = None) => None
+
+ Update the first LUF source in the process so that LDAP-group-to-Zope-role mapping
+ is done.
+ This is done by calling the appropriate method in LUF and affecting all 'group_' roles
+ to the matching LDAP groups.
+ """
+ # Fetch all groups
+ groups = self.getGroupIds()
+
+ # Scan sources
+ for src in self.listUserSources():
+ if not src.meta_type == "LDAPUserFolder":
+ continue
+
+ # Delete all former group mappings
+ deletes = []
+ for (grp, role) in src.getGroupMappings():
+ if role.startswith('group_'):
+ deletes.append(grp)
+ src.manage_deleteGroupMappings(deletes)
+
+ # Append all group mappings if it can be done
+ ldap_groups = src.getGroups(attr = "cn")
+ for grp in groups:
+ if src._local_groups:
+ grp_name = grp
+ else:
+ grp_name = grp[len('group_'):]
+ Log(LOG_DEBUG, "cheching", grp_name, "in", ldap_groups, )
+ if not grp_name in ldap_groups:
+ continue
+ Log(LOG_DEBUG, "Map", grp, "to", grp_name)
+ src.manage_addGroupMapping(
+ grp_name,
+ grp,
+ )
+
+ # Return
+ if REQUEST:
+ return REQUEST.RESPONSE.redirect(
+ self.absolute_url() + "/manage_wizard",
+ )
+ updateLDAPUserFolderMapping = postonly(updateLDAPUserFolderMapping)
+
+
+ # #
+ # The Wizard Section #
+ # #
+
+ def listLDAPUserFolderMapping(self,):
+ """
+ listLDAPUserFolderMapping(self,) => utility method
+ """
+ ret = []
+ gruf_done = []
+ ldap_done = []
+
+ # Scan sources
+ for src in self.listUserSources():
+ if not src.meta_type == "LDAPUserFolder":
+ continue
+
+ # Get all GRUF & LDAP groups
+ if src._local_groups:
+ gruf_ids = self.getGroupIds()
+ else:
+ gruf_ids = self.getGroupIds()
+ ldap_mapping = src.getGroupMappings()
+ ldap_groups = src.getGroups(attr = "cn")
+ for grp,role in ldap_mapping:
+ if role in gruf_ids:
+ ret.append((role, grp))
+ gruf_done.append(role)
+ ldap_done.append(grp)
+ if not src._local_groups:
+ ldap_done.append(role)
+ for grp in ldap_groups:
+ if not grp in ldap_done:
+ ret.append((None, grp))
+ for grp in gruf_ids:
+ if not grp in gruf_done:
+ ret.append((grp, None))
+ Log(LOG_DEBUG, "return", ret)
+ return ret
+
+
+ security.declareProtected(Permissions.manage_users, "getInvalidMappings")
+ def getInvalidMappings(self,):
+ """
+ return true if LUF mapping looks good
+ """
+ wrong = []
+ grufs = []
+ for gruf, ldap in self.listLDAPUserFolderMapping():
+ if gruf and ldap:
+ continue
+ if not gruf:
+ continue
+ if gruf.startswith('group_'):
+ gruf = gruf[len('group_'):]
+ grufs.append(gruf)
+ for gruf, ldap in self.listLDAPUserFolderMapping():
+ if gruf and ldap:
+ continue
+ if not ldap:
+ continue
+ if ldap.startswith('group_'):
+ ldap = ldap[len('group_'):]
+ if ldap in grufs:
+ wrong.append(ldap)
+
+ return wrong
+
+ security.declareProtected(Permissions.manage_users, "getLUFSource")
+ def getLUFSource(self,):
+ """
+ getLUFSource(self,) => Helper to get a pointer to the LUF src.
+ Return None if not available
+ """
+ for src in self.listUserSources():
+ if src.meta_type == "LDAPUserFolder":
+ return src
+
+ security.declareProtected(Permissions.manage_users, "areLUFGroupsLocal")
+ def areLUFGroupsLocal(self,):
+ """return true if luf groups are stored locally"""
+ return hasattr(self.getLUFSource(), '_local_groups')
+
+
+ security.declareProtected(Permissions.manage_users, "haveLDAPGroupFolder")
+ def haveLDAPGroupFolder(self,):
+ """return true if LDAPGroupFolder is the groups source
+ """
+ return not not self.Groups.acl_users.meta_type == 'LDAPGroupFolder'
+
+ security.declarePrivate('searchGroups')
+ def searchGroups(self, **kw):
+ names = self.getUserNames(__include_users__ = 0, __groups_prefixed__ = 1)
+ return [{'id' : gn} for gn in names]
+
+
+
+class treeWrapper:
+ """
+ treeWrapper: Wrapper around user/group objects for the tree
+ """
+ def __init__(self, id, tree, parents = []):
+ """
+ __init__(self, id, tree, parents = []) => wraps the user object for dtml-tree
+ """
+ # Prepare self-contained information
+ self._id = id
+ self.name = tree[id]['name']
+ self.icon = tree[id]['icon']
+ self.is_group = tree[id]['is_group']
+ parents.append(id)
+ self.path = parents
+
+ # Prepare subobjects information
+ subobjects = []
+ for grp_id in tree.keys():
+ if id in tree[grp_id]['belongs_to']:
+ subobjects.append(treeWrapper(grp_id, tree, parents))
+ subobjects.sort(lambda x, y: cmp(x.sortId(), y.sortId()))
+ self.subobjects = subobjects
+
+ def id(self,):
+ return self.name
+
+ def sortId(self,):
+ if self.is_group:
+ return "__%s" % (self._id,)
+ else:
+ return self._id
+
+ def tpValues(self,):
+ """
+ Return 'subobjects'
+ """
+ return self.subobjects
+
+ def tpId(self,):
+ return self._id
+
+ def tpURL(self,):
+ return self.tpId()
+
+InitializeClass(GroupUserFolder)