Back to index

plone3  3.1.7
GRUFUser.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 
00020 """
00021 __version__ = "$Revision:  $"
00022 # $Source:  $
00023 # $Id: GRUFUser.py 40118 2007-04-01 15:13:44Z alecm $
00024 __docformat__ = 'restructuredtext'
00025 
00026 from copy import copy
00027 
00028 # fakes a method from a DTML File
00029 from Globals import MessageDialog, DTMLFile
00030 
00031 from AccessControl import ClassSecurityInfo
00032 from AccessControl import Permissions
00033 from AccessControl import getSecurityManager
00034 from Globals import InitializeClass
00035 from Acquisition import Implicit, aq_inner, aq_parent, aq_base
00036 from Globals import Persistent
00037 from AccessControl.Role import RoleManager
00038 from OFS.SimpleItem import Item
00039 from OFS.PropertyManager import PropertyManager
00040 from OFS import ObjectManager, SimpleItem
00041 from DateTime import DateTime
00042 from App import ImageFile
00043 import AccessControl.Role, webdav.Collection
00044 import Products
00045 import os
00046 import string
00047 import shutil
00048 import random
00049 from global_symbols import *
00050 import AccessControl
00051 from Products.GroupUserFolder import postonly
00052 import GRUFFolder
00053 import GroupUserFolder
00054 from AccessControl.PermissionRole \
00055   import _what_not_even_god_should_do, rolesForPermissionOn
00056 from ComputedAttribute import ComputedAttribute
00057 
00058 
00059 import os
00060 import traceback
00061 
00062 from interfaces.IUserFolder import IUser, IGroup
00063 
00064 _marker = ['INVALID_VALUE']
00065 
00066 # NOTE : _what_not_even_god_should_do is a specific permission defined by ZOPE
00067 # that indicates that something has not to be done within Zope.
00068 # This value is given to the ACCESS_NONE directive of a SecurityPolicy.
00069 # It's rarely used within Zope BUT as it is documented (in AccessControl)
00070 # and may be used by third-party products, we have to process it.
00071 
00072 
00073 #GROUP_PREFIX is a constant
00074 
00075 class GRUFUserAtom(AccessControl.User.BasicUser, Implicit): 
00076     """
00077     Base class for all GRUF-catched User objects.
00078     There's, alas, many copy/paste from AccessControl.BasicUser...
00079     """
00080     security = ClassSecurityInfo()
00081 
00082     security.declarePrivate('_setUnderlying')
00083     def _setUnderlying(self, user):
00084         """
00085         _setUnderlying(self, user) => Set the GRUFUser properties to
00086         the underlying user's one.
00087         Be careful that any change to the underlying user won't be
00088         reported here. $$$ We don't know yet if User object are
00089         transaction-persistant or not...
00090         """
00091         self._original_name   = user.getUserName()
00092         self._original_password = user._getPassword()
00093         self._original_roles = user.getRoles()
00094         self._original_domains = user.getDomains()
00095         self._original_id = user.getId()
00096         self.__underlying__ = user # Used for authenticate() and __getattr__
00097 
00098 
00099     # ----------------------------
00100     # Public User object interface
00101     # ----------------------------
00102 
00103     # Maybe allow access to unprotected attributes. Note that this is
00104     # temporary to avoid exposing information but without breaking
00105     # everyone's current code. In the future the security will be
00106     # clamped down and permission-protected here. Because there are a
00107     # fair number of user object types out there, this method denies
00108     # access to names that are private parts of the standard User
00109     # interface or implementation only. The other approach (only
00110     # allowing access to public names in the User interface) would
00111     # probably break a lot of other User implementations with extended
00112     # functionality that we cant anticipate from the base scaffolding.
00113 
00114     security.declarePrivate('__init__')
00115     def __init__(self, underlying_user, GRUF, isGroup, source_id, ):
00116         # When calling, set isGroup it to TRUE if this user represents a group
00117         self._setUnderlying(underlying_user)
00118         self._isGroup = isGroup
00119         self._GRUF = GRUF
00120         self._source_id = source_id
00121         self.id = self._original_id
00122         # Store the results of getRoles and getGroups. Initially set to None,
00123         # set to a list after the methods are first called.
00124         # If you are caching users you want to clear these.
00125         self.clearCachedGroupsAndRoles()
00126 
00127     security.declarePrivate('clearCachedGroupsAndRoles')
00128     def clearCachedGroupsAndRoles(self, underlying_user = None):
00129         self._groups = None
00130         self._user_roles = None
00131         self._group_roles = None
00132         self._all_roles = None
00133         if underlying_user:
00134             self._setUnderlying(underlying_user)
00135         self._original_user_roles = None
00136 
00137     security.declarePublic('isGroup')
00138     def isGroup(self,):
00139         """Return 1 if this user is a group abstraction"""
00140         return self._isGroup
00141 
00142     security.declarePublic('getUserSourceId')
00143     def getUserSourceId(self,):
00144         """
00145         getUserSourceId(self,) => string
00146         Return the GRUF's GRUFUsers folder used to fetch this user.
00147         """
00148         return self._source_id
00149 
00150     security.declarePrivate('getGroupNames')
00151     def getGroupNames(self,):
00152         """..."""
00153         ret = self._getGroups(no_recurse = 1)
00154         return map(lambda x: x[GROUP_PREFIX_LEN:], ret)
00155 
00156     security.declarePrivate('getGroupIds')
00157     def getGroupIds(self,):
00158         """..."""
00159         return list(self._getGroups(no_recurse = 1))
00160 
00161     security.declarePrivate("getAllGroups")
00162     def getAllGroups(self,):
00163         """Same as getAllGroupNames()"""
00164         return self.getAllGroupIds()
00165 
00166     security.declarePrivate('getAllGroupNames')
00167     def getAllGroupNames(self,):
00168         """..."""
00169         ret = self._getGroups()
00170         return map(lambda x: x[GROUP_PREFIX_LEN:], ret)
00171 
00172     security.declarePrivate('getAllGroupIds')
00173     def getAllGroupIds(self,):
00174         """..."""
00175         return list(self._getGroups())
00176 
00177     security.declarePrivate('getGroups')
00178     def getGroups(self, *args, **kw):
00179         """..."""
00180         ret = self._getGroups(*args, **kw)
00181         return list(ret)
00182 
00183     security.declarePrivate("getImmediateGroups")
00184     def getImmediateGroups(self,):
00185         """
00186         Return NON-TRANSITIVE groups
00187         """
00188         ret = self._getGroups(no_recurse = 1)
00189         return list(ret)
00190     
00191     def _getGroups(self, no_recurse = 0, already_done = None, prefix = GROUP_PREFIX):
00192         """
00193         getGroups(self, no_recurse = 0, already_done = None, prefix = GROUP_PREFIX) => list of strings
00194         
00195         If this user is a user (uh, uh), get its groups.
00196         THIS METHODS NOW SUPPORTS NESTED GROUPS ! :-)
00197         The already_done parameter prevents infite recursions.
00198         Keep it as it is, never give it a value.
00199 
00200         If no_recurse is true, return only first level groups
00201 
00202         This method is private and should remain so.
00203         """
00204         if already_done is None:
00205             already_done = []
00206 
00207         # List this user's roles. We consider that roles starting 
00208         # with GROUP_PREFIX are in fact groups, and thus are 
00209         # returned (prefixed).
00210         if self._groups is not None:
00211             return self._groups
00212 
00213         # Populate cache if necessary
00214         if self._original_user_roles is None:
00215             self._original_user_roles = self.__underlying__.getRoles()
00216 
00217         # Scan roles to find groups
00218         ret = []
00219         for role in self._original_user_roles:
00220             # Inspect group-like roles
00221             if role.startswith(prefix):
00222 
00223                 # Prevent infinite recursion
00224                 if self._isGroup and role in already_done:
00225                     continue
00226 
00227                 # Get the underlying group
00228                 grp = self.aq_parent.getUser(role)
00229                 if not grp:
00230                     continue    # Invalid group
00231 
00232                 # Do not add twice the current group
00233                 if role in ret:
00234                     continue
00235 
00236                 # Append its nested groups (if recurse is asked)
00237                 ret.append(role)
00238                 if no_recurse:
00239                     continue
00240                 for extend in grp.getGroups(already_done = ret):
00241                     if not extend in ret:
00242                         ret.append(extend)
00243 
00244         # Return the groups
00245         self._groups = tuple(ret)
00246         return self._groups
00247 
00248 
00249     security.declarePrivate('getGroupsWithoutPrefix')
00250     def getGroupsWithoutPrefix(self, **kw):
00251         """
00252         Same as getGroups but return them without a prefix.
00253         """
00254         ret = []
00255         for group in self.getGroups(**kw):
00256           if group.startswith(GROUP_PREFIX):
00257             ret.append(group[len(GROUP_PREFIX):])
00258         return ret
00259 
00260     security.declarePublic('getUserNameWithoutGroupPrefix')
00261     def getUserNameWithoutGroupPrefix(self):
00262         """Return the username of a user without a group prefix"""
00263         if self.isGroup() and \
00264           self._original_name[:len(GROUP_PREFIX)] == GROUP_PREFIX:
00265             return self._original_name[len(GROUP_PREFIX):]
00266         return self._original_name
00267 
00268     security.declarePublic('getUserId')
00269     def getUserId(self):
00270         """Return the user id of a user"""
00271         if self.isGroup() and \
00272           not self._original_name[:len(GROUP_PREFIX)] == GROUP_PREFIX:
00273             return "%s%s" % (GROUP_PREFIX, self._original_name )
00274         return self._original_name
00275 
00276     security.declarePublic("getName")
00277     def getName(self,):
00278         """Get user's or group's name.
00279         For a user, the name can be set by the underlying user folder but usually id == name.
00280         For a group, the ID is prefixed, but the NAME is NOT prefixed by 'group_'.
00281         """
00282         return self.getUserNameWithoutGroupPrefix()
00283 
00284     security.declarePublic("getUserName")
00285     def getUserName(self,):
00286         """Alias for getName()"""
00287         return self.getUserNameWithoutGroupPrefix()
00288     
00289     security.declarePublic('getId')
00290     def getId(self, unprefixed = 0):
00291         """Get the ID of the user. The ID can be used, at least from
00292         Python, to get the user from the user's UserDatabase
00293         """
00294         # Return the right id
00295         if self.isGroup() and not self._original_name.startswith(GROUP_PREFIX) and not unprefixed:
00296             return "%s%s" % (GROUP_PREFIX, self._original_name)
00297         return self._original_name
00298 
00299     security.declarePublic('getRoles')
00300     def getRoles(self):
00301         """
00302         Return the list (tuple) of roles assigned to a user.
00303         THIS IS WHERE THE ATHENIANS REACHED !
00304         """
00305         if self._all_roles is not None:
00306             return self._all_roles
00307 
00308         # Return user and groups roles
00309         self._all_roles = GroupUserFolder.unique(self.getUserRoles() + self.getGroupRoles())
00310         return self._all_roles
00311 
00312     security.declarePublic('getUserRoles')
00313     def getUserRoles(self):
00314         """
00315         returns the roles defined for the user without the group roles
00316         """
00317         if self._user_roles is not None:
00318             return self._user_roles
00319         prefix = GROUP_PREFIX
00320         if self._original_user_roles is None:
00321             self._original_user_roles = self.__underlying__.getRoles()
00322         self._user_roles = tuple([r for r in self._original_user_roles if not r.startswith(prefix)])
00323         return self._user_roles
00324 
00325     security.declarePublic("getGroupRoles")
00326     def getGroupRoles(self,):
00327         """
00328         Return the tuple of roles belonging to this user's group(s)
00329         """
00330         if self._group_roles is not None:
00331             return self._group_roles
00332         ret = []
00333         acl_users = self._GRUF.acl_users 
00334         groups = acl_users.getGroupIds()      # XXX We can have a cache here
00335         
00336         for group in self.getGroups():
00337             if not group in groups:
00338                 Log("Group", group, "is invalid. Ignoring.")
00339                 # This may occur when groups are deleted
00340                 # Ignored silently
00341                 continue
00342             ret.extend(acl_users.getGroup(group).getUserRoles())
00343 
00344         self._group_roles = GroupUserFolder.unique(ret)
00345         return self._group_roles
00346 
00347     security.declarePublic('getRolesInContext')
00348     def getRolesInContext(self, object, userid = None):
00349         """
00350         Return the list of roles assigned to the user,
00351         including local roles assigned in context of
00352         the passed in object.
00353         """
00354         if not userid:
00355             userid=self.getId()
00356 
00357         roles = {}
00358         for role in self.getRoles():
00359             roles[role] = 1
00360 
00361         user_groups = self.getGroups()
00362 
00363         inner_obj = getattr(object, 'aq_inner', object)
00364         while 1:
00365             # Usual local roles retreiving
00366             local_roles = getattr(inner_obj, '__ac_local_roles__', None)
00367             if local_roles:
00368                 if callable(local_roles):
00369                     local_roles = local_roles()
00370                 dict = local_roles or {}
00371 
00372                 for role in dict.get(userid, []):
00373                     roles[role] = 1
00374 
00375                 # Get roles & local roles for groups
00376                 # This handles nested groups as well
00377                 for groupid in user_groups:
00378                     for role in dict.get(groupid, []):
00379                         roles[role] = 1
00380                         
00381             # LocalRole blocking
00382             obj = getattr(inner_obj, 'aq_base', inner_obj)
00383             if getattr(obj, '__ac_local_roles_block__', None):
00384                 break
00385 
00386             # Loop management
00387             inner = getattr(inner_obj, 'aq_inner', inner_obj)
00388             parent = getattr(inner, 'aq_parent', None)
00389             if parent is not None:
00390                 inner_obj = parent
00391                 continue
00392             if hasattr(inner_obj, 'im_self'):
00393                 inner_obj=inner_obj.im_self
00394                 inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
00395                 continue
00396             break
00397 
00398         return tuple(roles.keys())
00399 
00400     security.declarePublic('getDomains')
00401     def getDomains(self):
00402         """Return the list of domain restrictions for a user"""
00403         return self._original_domains
00404 
00405 
00406     security.declarePrivate("getProperty")
00407     def getProperty(self, name, default=_marker):
00408         """getProperty(self, name) => return property value or raise AttributeError
00409         """
00410         # Try to do an attribute lookup on the underlying user object
00411         v = getattr(self.__underlying__, name, default)
00412         if v is _marker:
00413             raise AttributeError, name
00414         return v
00415 
00416     security.declarePrivate("hasProperty")
00417     def hasProperty(self, name):
00418         """hasProperty"""
00419         return hasattr(self.__underlying__, name)
00420 
00421     security.declarePrivate("setProperty")
00422     def setProperty(self, name, value):
00423         """setProperty => Try to set the property...
00424         By now, it's available only for LDAPUserFolder
00425         """
00426         # Get actual source
00427         src = self._GRUF.getUserSource(self.getUserSourceId())
00428         if not src:
00429             raise RuntimeError, "Invalid or missing user source for '%s'." % (self.getId(),)
00430 
00431         # LDAPUserFolder => specific API.
00432         if hasattr(src, "manage_setUserProperty"):
00433             # Unmap pty name if necessary, get it in the schema
00434             ldapname = None
00435             for schema in src.getSchemaConfig().values():
00436                 if schema["ldap_name"] == name:
00437                     ldapname = schema["ldap_name"]
00438                 if schema["public_name"] == name:
00439                     ldapname = schema["ldap_name"]
00440                     break
00441 
00442             # If we didn't find it, we skip it
00443             if ldapname is None:
00444                 raise KeyError, "Invalid LDAP attribute: '%s'." % (name, )
00445             
00446             # Edit user
00447             user_dn = src._find_user_dn(self.getUserName())
00448             src.manage_setUserProperty(user_dn, ldapname, value)
00449 
00450             # Expire the underlying user object
00451             self.__underlying__ = src.getUser(self.getId())
00452             if not self.__underlying__:
00453                 raise RuntimeError, "Error while setting property of '%s'." % (self.getId(),)
00454 
00455         # Now we check if the property has been changed
00456         if not self.hasProperty(name):
00457             raise NotImplementedError, "Property setting is not supported for '%s'." % (name,)
00458         v = self._GRUF.getUserById(self.getId()).getProperty(name)
00459         if not v == value:
00460             Log(LOG_DEBUG, "Property '%s' for user '%s' should be '%s' and not '%s'" % (
00461                 name, self.getId(), value, v,
00462                 ))
00463             raise NotImplementedError, "Property setting is not supported for '%s'." % (name,)
00464 
00465     # ------------------------------
00466     # Internal User object interface
00467     # ------------------------------
00468 
00469     security.declarePrivate('authenticate')
00470     def authenticate(self, password, request):
00471         # We prevent groups from authenticating
00472         if self._isGroup:
00473             return None
00474         return self.__underlying__.authenticate(password, request)
00475 
00476 
00477     security.declarePublic('allowed')
00478     def allowed(self, object, object_roles=None):
00479         """Check whether the user has access to object. The user must
00480            have one of the roles in object_roles to allow access."""
00481 
00482         if object_roles is _what_not_even_god_should_do:
00483             return 0
00484 
00485         # Short-circuit the common case of anonymous access.
00486         if object_roles is None or 'Anonymous' in object_roles:
00487             return 1
00488 
00489         # Provide short-cut access if object is protected by 'Authenticated'
00490         # role and user is not nobody
00491         if 'Authenticated' in object_roles and \
00492             (self.getUserName() != 'Anonymous User'):
00493             return 1
00494 
00495         # Check for ancient role data up front, convert if found.
00496         # This should almost never happen, and should probably be
00497         # deprecated at some point.
00498         if 'Shared' in object_roles:
00499             object_roles = self._shared_roles(object)
00500             if object_roles is None or 'Anonymous' in object_roles:
00501                 return 1
00502 
00503 
00504         # Trying to make some speed improvements, changes starts here.
00505         # Helge Tesdal, Plone Solutions AS, http://www.plonesolutions.com
00506         # We avoid using the getRoles() and getRolesInContext() methods to be able
00507         # to short circuit.
00508 
00509         # Dict for faster lookup and avoiding duplicates
00510         object_roles_dict = {}
00511         for role in object_roles:
00512             object_roles_dict[role] = 1
00513 
00514         if [role for role in self.getUserRoles() if object_roles_dict.has_key(role)]:
00515             if self._check_context(object):
00516                 return 1
00517             return None
00518 
00519         # Try the top level group roles.
00520         if [role for role in self.getGroupRoles() if object_roles_dict.has_key(role)]:
00521             if self._check_context(object):
00522                 return 1
00523             return None
00524 
00525         user_groups = self.getGroups()
00526         # No luck on the top level, try local roles
00527         inner_obj = getattr(object, 'aq_inner', object)
00528         userid = self.getId()
00529         while 1:
00530             local_roles = getattr(inner_obj, '__ac_local_roles__', None)
00531             if local_roles:
00532                 if callable(local_roles):
00533                     local_roles = local_roles()
00534                 dict = local_roles or {}
00535 
00536                 if [role for role in dict.get(userid, []) if object_roles_dict.has_key(role)]:
00537                     if self._check_context(object):
00538                         return 1
00539                     return None
00540 
00541                 # Get roles & local roles for groups
00542                 # This handles nested groups as well
00543                 for groupid in user_groups:
00544                     if [role for role in dict.get(groupid, []) if object_roles_dict.has_key(role)]:
00545                         if self._check_context(object):
00546                             return 1
00547                         return None
00548 
00549             # LocalRole blocking
00550             obj = getattr(inner_obj, 'aq_base', inner_obj)
00551             if getattr(obj, '__ac_local_roles_block__', None):
00552                 break
00553 
00554             # Loop control
00555             inner = getattr(inner_obj, 'aq_inner', inner_obj)
00556             parent = getattr(inner, 'aq_parent', None)
00557             if parent is not None:
00558                 inner_obj = parent
00559                 continue
00560             if hasattr(inner_obj, 'im_self'):
00561                 inner_obj=inner_obj.im_self
00562                 inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
00563                 continue
00564             break
00565         return None
00566 
00567 
00568     security.declarePublic('hasRole')
00569     def hasRole(self, *args, **kw):
00570         """hasRole is an alias for 'allowed' and has been deprecated.
00571 
00572         Code still using this method should convert to either 'has_role' or
00573         'allowed', depending on the intended behaviour.
00574 
00575         """
00576         import warnings
00577         warnings.warn('BasicUser.hasRole is deprecated, please use '
00578             'BasicUser.allowed instead; hasRole was an alias for allowed, but '
00579             'you may have ment to use has_role.', DeprecationWarning)
00580         return self.allowed(*args, **kw)
00581 
00582     #                                                           #
00583     #               Underlying user object support              #
00584     #                                                           #
00585     
00586     def __getattr__(self, name):
00587         # This will call the underlying object's methods
00588         # if they are not found in this user object.
00589         # We will have to check Chris' http://www.plope.com/Members/chrism/plone_on_zope_head
00590         # to make it work with Zope HEAD.
00591         ret = getattr(self.__dict__['__underlying__'], name)
00592         return ret
00593 
00594     security.declarePublic('getUnwrappedUser')
00595     def getUnwrappedUser(self,):
00596         """
00597         same as GRUF.getUnwrappedUser, but implicitly with this particular user
00598         """
00599         return self.__dict__['__underlying__']
00600 
00601     def __getitem__(self, name):
00602         # This will call the underlying object's methods
00603         # if they are not found in this user object.
00604         return self.__underlying__[name]
00605 
00606     #                                                           #
00607     #                      HTML link support                    #
00608     #                                                           #
00609 
00610     def asHTML(self, implicit=0):
00611         """
00612         asHTML(self, implicit=0) => HTML string
00613         Used to generate homogeneous links for management screens
00614         """
00615         acl_users = self.acl_users
00616         if self.isGroup():
00617             color = acl_users.group_color
00618             kind = "Group"
00619         else:
00620             color = acl_users.user_color
00621             kind = "User"
00622 
00623         ret = '''<a href="%(href)s" alt="%(alt)s"><font color="%(color)s">%(name)s</font></a>''' % {
00624             "color": color,
00625             "href": "%s/%s/manage_workspace?FORCE_USER=1" % (acl_users.absolute_url(), self.getId(), ),
00626             "name": self.getUserNameWithoutGroupPrefix(),
00627             "alt": "%s (%s)" % (self.getUserNameWithoutGroupPrefix(), kind, ),
00628             }
00629         if implicit:
00630             return "<i>%s</i>" % ret
00631         return ret
00632 
00633     
00634     security.declarePrivate("isInGroup")
00635     def isInGroup(self, groupid):
00636         """Return true if the user is member of the specified group id
00637         (including transitive groups)"""
00638         return groupid in self.getAllGroupIds()
00639 
00640     security.declarePublic("getRealId")
00641     def getRealId(self,):
00642         """Return id WITHOUT group prefix
00643         """
00644         raise NotImplementedError, "Must be derived in subclasses"
00645     
00646 
00647 class GRUFUser(GRUFUserAtom):
00648     """
00649     This is the class for actual user objects
00650     """
00651     __implements__ = (IUser, )
00652 
00653     security = ClassSecurityInfo()
00654 
00655     #                                                           #
00656     #                     User Mutation                         #
00657     #                                                           #
00658 
00659     security.declarePublic('changePassword')
00660     def changePassword(self, password, REQUEST=None):
00661         """Set the user's password. This method performs its own security checks"""
00662         # Check security
00663         user = getSecurityManager().getUser()
00664         if not user.has_permission(Permissions.manage_users, self._GRUF):       # Is manager ?
00665             if user.__class__.__name__ != "GRUFUser":
00666                 raise "Unauthorized", "You cannot change someone else's password."
00667             if not user.getId() == self.getId():    # Is myself ?
00668                 raise "Unauthorized", "You cannot change someone else's password."
00669         
00670         # Just do it
00671         self.clearCachedGroupsAndRoles()
00672         return self._GRUF.userSetPassword(self.getId(), password)
00673     changePassword = postonly(changePassword)
00674 
00675     security.declarePrivate("setRoles")
00676     def setRoles(self, roles):
00677         """Change the roles of a user atom.
00678         """
00679         self.clearCachedGroupsAndRoles()
00680         return self._GRUF.userSetRoles(self.getId(), roles)
00681 
00682     security.declarePrivate("addRole")
00683     def addRole(self, role):
00684         """Append a role for a user atom
00685         """
00686         self.clearCachedGroupsAndRoles()
00687         return self._GRUF.userAddRole(self.getId(), role)
00688 
00689     security.declarePrivate("removeRole")
00690     def removeRole(self, role):
00691         """Remove the role of a user atom
00692         """
00693         self.clearCachedGroupsAndRoles()
00694         return self._GRUF.userRemoveRole(self.getId(), role)
00695 
00696     security.declarePrivate("setPassword")
00697     def setPassword(self, newPassword):
00698         """Set the password of a user
00699         """
00700         self.clearCachedGroupsAndRoles()
00701         return self._GRUF.userSetPassword(self.getId(), newPassword)
00702 
00703     security.declarePrivate("setDomains")
00704     def setDomains(self, domains):
00705         """Set domains for a user
00706         """
00707         self.clearCachedGroupsAndRoles()
00708         self._GRUF.userSetDomains(self.getId(), domains)
00709         self._original_domains = self._GRUF.userGetDomains(self.getId())
00710 
00711     security.declarePrivate("addDomain")
00712     def addDomain(self, domain):
00713         """Append a domain to a user
00714         """
00715         self.clearCachedGroupsAndRoles()
00716         self._GRUF.userAddDomain(self.getId(), domain)
00717         self._original_domains = self._GRUF.userGetDomains(self.getId())
00718 
00719     security.declarePrivate("removeDomain")
00720     def removeDomain(self, domain):
00721         """Remove a domain from a user
00722         """
00723         self.clearCachedGroupsAndRoles()
00724         self._GRUF.userRemoveDomain(self.getId(), domain)
00725         self._original_domains = self._GRUF.userGetDomains(self.getId())
00726 
00727     security.declarePrivate("setGroups")
00728     def setGroups(self, groupnames):
00729         """Set the groups of a user
00730         """
00731         self.clearCachedGroupsAndRoles()
00732         return self._GRUF.userSetGroups(self.getId(), groupnames)
00733 
00734     security.declarePrivate("addGroup")
00735     def addGroup(self, groupname):
00736         """add a group to a user atom
00737         """
00738         self.clearCachedGroupsAndRoles()
00739         return self._GRUF.userAddGroup(self.getId(), groupname)
00740 
00741     security.declarePrivate("removeGroup")
00742     def removeGroup(self, groupname):
00743         """remove a group from a user atom.
00744         """
00745         self.clearCachedGroupsAndRoles()
00746         return self._GRUF.userRemoveGroup(self.getId(), groupname)
00747 
00748     security.declarePrivate('_getPassword')
00749     def _getPassword(self):
00750         """Return the password of the user."""
00751         return self._original_password
00752 
00753     security.declarePublic("getRealId")
00754     def getRealId(self,):
00755         """Return id WITHOUT group prefix
00756         """
00757         return self.getId()
00758 
00759 
00760 class GRUFGroup(GRUFUserAtom):
00761     """
00762     This is the class for actual group objects
00763     """
00764     __implements__ = (IGroup, )
00765     
00766     security = ClassSecurityInfo()
00767     
00768     security.declarePublic("getRealId")
00769     def getRealId(self,):
00770         """Return group id WITHOUT group prefix
00771         """
00772         return self.getId()[len(GROUP_PREFIX):]
00773     
00774     def _getLDAPMemberIds(self,):
00775         """
00776         _getLDAPMemberIds(self,) => Uses LDAPUserFolder to find
00777         users in a group.
00778         """
00779         # Find the right source
00780         gruf = self.aq_parent
00781         src = None
00782         for src in gruf.listUserSources():
00783             if not src.meta_type == "LDAPUserFolder":
00784                 continue
00785         if src is None:
00786             Log(LOG_DEBUG, "No LDAPUserFolder source found")
00787             return []
00788 
00789         # Find the group in LDAP
00790         groups = src.getGroups()
00791         groupid = self.getId()
00792         grp = [ group for group in groups if group[0] == self.getId() ]
00793         if not grp:
00794             Log(LOG_DEBUG, "No such group ('%s') found." % (groupid,))
00795             return []
00796 
00797         # Return the grup member ids
00798         userids = src.getGroupedUsers(grp)
00799         Log(LOG_DEBUG, "We've found %d users belonging to the group '%s'" % (len(userids), grp), )
00800         return userids
00801     
00802     def _getMemberIds(self, users = 1, groups = 1, transitive = 1, ):
00803         """
00804         Return the member ids (users and groups) of the atoms of this group.
00805         Transitiveness attribute is ignored with LDAP (no nested groups with
00806         LDAP anyway).
00807         This method now uses a shortcut to fetch members of an LDAP group
00808         (stored either within Zope or within your LDAP server)
00809         """
00810         # Initial parameters.
00811         # We fetch the users/groups list depending on what we search,
00812         # and carefuly avoiding to use LDAP sources.
00813         gruf = self.aq_parent
00814         ldap_sources = []
00815         lst = []
00816         if transitive:
00817             method = "getAllGroupIds"
00818         else:
00819             method = "getGroupIds"
00820         if users:
00821             for src in gruf.listUserSources():
00822                 if src.meta_type == 'LDAPUserFolder':
00823                     ldap_sources.append(src)
00824                     continue # We'll fetch 'em later
00825                 lst.extend(src.getUserNames())
00826         if groups:
00827             lst.extend(gruf.getGroupIds())
00828 
00829         # First extraction for regular user sources.
00830         # This part is very very long, and the more users you have,
00831         # the longer this method will be.
00832         groupid = self.getId()
00833         groups_mapping = {}
00834         for u in lst:
00835             usr = gruf.getUser(u)
00836             if not usr:
00837                 groups_mapping[u] = []
00838                 Log(LOG_WARNING, "Invalid user retreiving:", u)
00839             else:
00840                 groups_mapping[u] = getattr(usr, method)()
00841         members = [u for u in lst if groupid in groups_mapping[u]]
00842 
00843         # If we have LDAP sources, we fetch user-group mapping inside directly
00844         groupid = self.getId()
00845         for src in ldap_sources:
00846             groups = src.getGroups()
00847             # With LDAPUserFolder >= 2.7 we need to add GROUP_PREFIX to group_name
00848             # We keep backward compatibility
00849             grp = [ group for group in groups if group[0] == self.getId() or \
00850                                                  GROUP_PREFIX + group[0] == self.getId()]
00851             if not grp:
00852                 Log(LOG_DEBUG, "No such group ('%s') found." % (groupid,))
00853                 continue
00854 
00855             # Return the grup member ids
00856             userids = [ str(u) for u in src.getGroupedUsers(grp) ]
00857             Log(LOG_DEBUG, "We've found %d users belonging to the group '%s'" % (len(userids), grp), )
00858             members.extend(userids)
00859 
00860         # Return the members we've found
00861         return members
00862 
00863     security.declarePrivate("getMemberIds")
00864     def getMemberIds(self, transitive = 1, ):
00865         "Return member ids of this group, including or not transitive groups."
00866         return self._getMemberIds(transitive = transitive)
00867 
00868     security.declarePrivate("getUserMemberIds")
00869     def getUserMemberIds(self, transitive = 1, ):
00870         """Return the member ids (users only) of the users of this group"""
00871         return self._getMemberIds(groups = 0, transitive = transitive)
00872     
00873     security.declarePrivate("getGroupMemberIds")
00874     def getGroupMemberIds(self, transitive = 1, ):
00875         """Return the members ids (groups only) of the groups of this group"""
00876         return self._getMemberIds(users = 0, transitive = transitive)
00877     
00878     security.declarePrivate("hasMember")
00879     def hasMember(self, id):
00880         """Return true if the specified atom id is in the group.
00881         This is the contrary of IUserAtom.isInGroup(groupid)"""
00882         gruf = self.aq_parent
00883         return id in gruf.getMemberIds(self.getId())
00884     
00885     security.declarePrivate("addMember")
00886     def addMember(self, userid):
00887         """Add a user the the current group"""
00888         gruf = self.aq_parent
00889         groupid = self.getId()
00890         usr = gruf.getUser(userid)
00891         if not usr:
00892             raise ValueError, "Invalid user: '%s'" % (userid, )
00893         if not groupid in gruf.getGroupNames() + gruf.getGroupIds():
00894             raise ValueError, "Invalid group: '%s'" % (groupid, )
00895         groups = list(usr.getGroups())
00896         groups.append(groupid)
00897         groups = GroupUserFolder.unique(groups)
00898         return gruf._updateUser(userid, groups = groups)
00899 
00900     security.declarePrivate("removeMember")
00901     def removeMember(self, userid):
00902         """Remove a user from the current group"""
00903         gruf = self.aq_parent
00904         groupid = self.getId()
00905 
00906         # Check the user
00907         usr = gruf.getUser(userid)
00908         if not usr:
00909             raise ValueError, "Invalid user: '%s'" % (userid, )
00910 
00911         # Now, remove the group
00912         groups = list(usr.getImmediateGroups())
00913         if groupid in groups:
00914             groups.remove(groupid)
00915             gruf._updateUser(userid, groups = groups)
00916         else:
00917             raise ValueError, "User '%s' doesn't belong to group '%s'" % (userid, groupid, )
00918     
00919     security.declarePrivate("setMembers")
00920     def setMembers(self, userids):
00921         """Set the members of the group
00922         """
00923         member_ids = self.getMemberIds()
00924         all_ids = copy(member_ids)
00925         all_ids.extend(userids)
00926         groupid = self.getId()
00927         for id in all_ids:
00928             if id in member_ids and id not in userids:
00929                 self.removeMember(id)
00930             elif id not in member_ids and id in userids:
00931                 self.addMember(id)
00932 
00933 
00934 InitializeClass(GRUFUser)
00935 InitializeClass(GRUFGroup)