eggification
[GroupUserFolder.git] / Products / GroupUserFolder / GRUFUser.py
1 # -*- coding: utf-8 -*-
2 ## GroupUserFolder
3 ## Copyright (C)2006 Ingeniweb
4
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 2 of the License, or
8 ## (at your option) any later version.
9
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
14
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program; see the file COPYING. If not, write to the
17 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 """
19
20 """
21 __version__ = "$Revision: $"
22 # $Source: $
23 # $Id: GRUFUser.py 40118 2007-04-01 15:13:44Z alecm $
24 __docformat__ = 'restructuredtext'
25
26 from copy import copy
27
28 # fakes a method from a DTML File
29 from Globals import MessageDialog, DTMLFile
30
31 from AccessControl import ClassSecurityInfo
32 from AccessControl import Permissions
33 from AccessControl import getSecurityManager
34 from Globals import InitializeClass
35 from Acquisition import Implicit, aq_inner, aq_parent, aq_base
36 from Globals import Persistent
37 from AccessControl.Role import RoleManager
38 from OFS.SimpleItem import Item
39 from OFS.PropertyManager import PropertyManager
40 from OFS import ObjectManager, SimpleItem
41 from DateTime import DateTime
42 from App import ImageFile
43 import AccessControl.Role, webdav.Collection
44 import Products
45 import os
46 import string
47 import shutil
48 import random
49 from global_symbols import *
50 import AccessControl
51 from Products.GroupUserFolder import postonly
52 import GRUFFolder
53 import GroupUserFolder
54 from AccessControl.PermissionRole \
55 import _what_not_even_god_should_do, rolesForPermissionOn
56 from ComputedAttribute import ComputedAttribute
57
58
59 import os
60 import traceback
61
62 from interfaces.IUserFolder import IUser, IGroup
63
64 _marker = ['INVALID_VALUE']
65
66 # NOTE : _what_not_even_god_should_do is a specific permission defined by ZOPE
67 # that indicates that something has not to be done within Zope.
68 # This value is given to the ACCESS_NONE directive of a SecurityPolicy.
69 # It's rarely used within Zope BUT as it is documented (in AccessControl)
70 # and may be used by third-party products, we have to process it.
71
72
73 #GROUP_PREFIX is a constant
74
75 class GRUFUserAtom(AccessControl.User.BasicUser, Implicit):
76 """
77 Base class for all GRUF-catched User objects.
78 There's, alas, many copy/paste from AccessControl.BasicUser...
79 """
80 security = ClassSecurityInfo()
81
82 security.declarePrivate('_setUnderlying')
83 def _setUnderlying(self, user):
84 """
85 _setUnderlying(self, user) => Set the GRUFUser properties to
86 the underlying user's one.
87 Be careful that any change to the underlying user won't be
88 reported here. $$$ We don't know yet if User object are
89 transaction-persistant or not...
90 """
91 self._original_name = user.getUserName()
92 self._original_password = user._getPassword()
93 self._original_roles = user.getRoles()
94 self._original_domains = user.getDomains()
95 self._original_id = user.getId()
96 self.__underlying__ = user # Used for authenticate() and __getattr__
97
98
99 # ----------------------------
100 # Public User object interface
101 # ----------------------------
102
103 # Maybe allow access to unprotected attributes. Note that this is
104 # temporary to avoid exposing information but without breaking
105 # everyone's current code. In the future the security will be
106 # clamped down and permission-protected here. Because there are a
107 # fair number of user object types out there, this method denies
108 # access to names that are private parts of the standard User
109 # interface or implementation only. The other approach (only
110 # allowing access to public names in the User interface) would
111 # probably break a lot of other User implementations with extended
112 # functionality that we cant anticipate from the base scaffolding.
113
114 security.declarePrivate('__init__')
115 def __init__(self, underlying_user, GRUF, isGroup, source_id, ):
116 # When calling, set isGroup it to TRUE if this user represents a group
117 self._setUnderlying(underlying_user)
118 self._isGroup = isGroup
119 self._GRUF = GRUF
120 self._source_id = source_id
121 self.id = self._original_id
122 # Store the results of getRoles and getGroups. Initially set to None,
123 # set to a list after the methods are first called.
124 # If you are caching users you want to clear these.
125 self.clearCachedGroupsAndRoles()
126
127 security.declarePrivate('clearCachedGroupsAndRoles')
128 def clearCachedGroupsAndRoles(self, underlying_user = None):
129 self._groups = None
130 self._user_roles = None
131 self._group_roles = None
132 self._all_roles = None
133 if underlying_user:
134 self._setUnderlying(underlying_user)
135 self._original_user_roles = None
136
137 security.declarePublic('isGroup')
138 def isGroup(self,):
139 """Return 1 if this user is a group abstraction"""
140 return self._isGroup
141
142 security.declarePublic('getUserSourceId')
143 def getUserSourceId(self,):
144 """
145 getUserSourceId(self,) => string
146 Return the GRUF's GRUFUsers folder used to fetch this user.
147 """
148 return self._source_id
149
150 security.declarePrivate('getGroupNames')
151 def getGroupNames(self,):
152 """..."""
153 ret = self._getGroups(no_recurse = 1)
154 return map(lambda x: x[GROUP_PREFIX_LEN:], ret)
155
156 security.declarePrivate('getGroupIds')
157 def getGroupIds(self,):
158 """..."""
159 return list(self._getGroups(no_recurse = 1))
160
161 security.declarePrivate("getAllGroups")
162 def getAllGroups(self,):
163 """Same as getAllGroupNames()"""
164 return self.getAllGroupIds()
165
166 security.declarePrivate('getAllGroupNames')
167 def getAllGroupNames(self,):
168 """..."""
169 ret = self._getGroups()
170 return map(lambda x: x[GROUP_PREFIX_LEN:], ret)
171
172 security.declarePrivate('getAllGroupIds')
173 def getAllGroupIds(self,):
174 """..."""
175 return list(self._getGroups())
176
177 security.declarePrivate('getGroups')
178 def getGroups(self, *args, **kw):
179 """..."""
180 ret = self._getGroups(*args, **kw)
181 return list(ret)
182
183 security.declarePrivate("getImmediateGroups")
184 def getImmediateGroups(self,):
185 """
186 Return NON-TRANSITIVE groups
187 """
188 ret = self._getGroups(no_recurse = 1)
189 return list(ret)
190
191 def _getGroups(self, no_recurse = 0, already_done = None, prefix = GROUP_PREFIX):
192 """
193 getGroups(self, no_recurse = 0, already_done = None, prefix = GROUP_PREFIX) => list of strings
194
195 If this user is a user (uh, uh), get its groups.
196 THIS METHODS NOW SUPPORTS NESTED GROUPS ! :-)
197 The already_done parameter prevents infite recursions.
198 Keep it as it is, never give it a value.
199
200 If no_recurse is true, return only first level groups
201
202 This method is private and should remain so.
203 """
204 if already_done is None:
205 already_done = []
206
207 # List this user's roles. We consider that roles starting
208 # with GROUP_PREFIX are in fact groups, and thus are
209 # returned (prefixed).
210 if self._groups is not None:
211 return self._groups
212
213 # Populate cache if necessary
214 if self._original_user_roles is None:
215 self._original_user_roles = self.__underlying__.getRoles()
216
217 # Scan roles to find groups
218 ret = []
219 for role in self._original_user_roles:
220 # Inspect group-like roles
221 if role.startswith(prefix):
222
223 # Prevent infinite recursion
224 if self._isGroup and role in already_done:
225 continue
226
227 # Get the underlying group
228 grp = self.aq_parent.getUser(role)
229 if not grp:
230 continue # Invalid group
231
232 # Do not add twice the current group
233 if role in ret:
234 continue
235
236 # Append its nested groups (if recurse is asked)
237 ret.append(role)
238 if no_recurse:
239 continue
240 for extend in grp.getGroups(already_done = ret):
241 if not extend in ret:
242 ret.append(extend)
243
244 # Return the groups
245 self._groups = tuple(ret)
246 return self._groups
247
248
249 security.declarePrivate('getGroupsWithoutPrefix')
250 def getGroupsWithoutPrefix(self, **kw):
251 """
252 Same as getGroups but return them without a prefix.
253 """
254 ret = []
255 for group in self.getGroups(**kw):
256 if group.startswith(GROUP_PREFIX):
257 ret.append(group[len(GROUP_PREFIX):])
258 return ret
259
260 security.declarePublic('getUserNameWithoutGroupPrefix')
261 def getUserNameWithoutGroupPrefix(self):
262 """Return the username of a user without a group prefix"""
263 if self.isGroup() and \
264 self._original_name[:len(GROUP_PREFIX)] == GROUP_PREFIX:
265 return self._original_name[len(GROUP_PREFIX):]
266 return self._original_name
267
268 security.declarePublic('getUserId')
269 def getUserId(self):
270 """Return the user id of a user"""
271 if self.isGroup() and \
272 not self._original_name[:len(GROUP_PREFIX)] == GROUP_PREFIX:
273 return "%s%s" % (GROUP_PREFIX, self._original_name )
274 return self._original_name
275
276 security.declarePublic("getName")
277 def getName(self,):
278 """Get user's or group's name.
279 For a user, the name can be set by the underlying user folder but usually id == name.
280 For a group, the ID is prefixed, but the NAME is NOT prefixed by 'group_'.
281 """
282 return self.getUserNameWithoutGroupPrefix()
283
284 security.declarePublic("getUserName")
285 def getUserName(self,):
286 """Alias for getName()"""
287 return self.getUserNameWithoutGroupPrefix()
288
289 security.declarePublic('getId')
290 def getId(self, unprefixed = 0):
291 """Get the ID of the user. The ID can be used, at least from
292 Python, to get the user from the user's UserDatabase
293 """
294 # Return the right id
295 if self.isGroup() and not self._original_name.startswith(GROUP_PREFIX) and not unprefixed:
296 return "%s%s" % (GROUP_PREFIX, self._original_name)
297 return self._original_name
298
299 security.declarePublic('getRoles')
300 def getRoles(self):
301 """
302 Return the list (tuple) of roles assigned to a user.
303 THIS IS WHERE THE ATHENIANS REACHED !
304 """
305 if self._all_roles is not None:
306 return self._all_roles
307
308 # Return user and groups roles
309 self._all_roles = GroupUserFolder.unique(self.getUserRoles() + self.getGroupRoles())
310 return self._all_roles
311
312 security.declarePublic('getUserRoles')
313 def getUserRoles(self):
314 """
315 returns the roles defined for the user without the group roles
316 """
317 if self._user_roles is not None:
318 return self._user_roles
319 prefix = GROUP_PREFIX
320 if self._original_user_roles is None:
321 self._original_user_roles = self.__underlying__.getRoles()
322 self._user_roles = tuple([r for r in self._original_user_roles if not r.startswith(prefix)])
323 return self._user_roles
324
325 security.declarePublic("getGroupRoles")
326 def getGroupRoles(self,):
327 """
328 Return the tuple of roles belonging to this user's group(s)
329 """
330 if self._group_roles is not None:
331 return self._group_roles
332 ret = []
333 acl_users = self._GRUF.acl_users
334 groups = acl_users.getGroupIds() # XXX We can have a cache here
335
336 for group in self.getGroups():
337 if not group in groups:
338 Log("Group", group, "is invalid. Ignoring.")
339 # This may occur when groups are deleted
340 # Ignored silently
341 continue
342 ret.extend(acl_users.getGroup(group).getUserRoles())
343
344 self._group_roles = GroupUserFolder.unique(ret)
345 return self._group_roles
346
347 security.declarePublic('getRolesInContext')
348 def getRolesInContext(self, object, userid = None):
349 """
350 Return the list of roles assigned to the user,
351 including local roles assigned in context of
352 the passed in object.
353 """
354 if not userid:
355 userid=self.getId()
356
357 roles = {}
358 for role in self.getRoles():
359 roles[role] = 1
360
361 user_groups = self.getGroups()
362
363 inner_obj = getattr(object, 'aq_inner', object)
364 while 1:
365 # Usual local roles retreiving
366 local_roles = getattr(inner_obj, '__ac_local_roles__', None)
367 if local_roles:
368 if callable(local_roles):
369 local_roles = local_roles()
370 dict = local_roles or {}
371
372 for role in dict.get(userid, []):
373 roles[role] = 1
374
375 # Get roles & local roles for groups
376 # This handles nested groups as well
377 for groupid in user_groups:
378 for role in dict.get(groupid, []):
379 roles[role] = 1
380
381 # LocalRole blocking
382 obj = getattr(inner_obj, 'aq_base', inner_obj)
383 if getattr(obj, '__ac_local_roles_block__', None):
384 break
385
386 # Loop management
387 inner = getattr(inner_obj, 'aq_inner', inner_obj)
388 parent = getattr(inner, 'aq_parent', None)
389 if parent is not None:
390 inner_obj = parent
391 continue
392 if hasattr(inner_obj, 'im_self'):
393 inner_obj=inner_obj.im_self
394 inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
395 continue
396 break
397
398 return tuple(roles.keys())
399
400 security.declarePublic('getDomains')
401 def getDomains(self):
402 """Return the list of domain restrictions for a user"""
403 return self._original_domains
404
405
406 security.declarePrivate("getProperty")
407 def getProperty(self, name, default=_marker):
408 """getProperty(self, name) => return property value or raise AttributeError
409 """
410 # Try to do an attribute lookup on the underlying user object
411 v = getattr(self.__underlying__, name, default)
412 if v is _marker:
413 raise AttributeError, name
414 return v
415
416 security.declarePrivate("hasProperty")
417 def hasProperty(self, name):
418 """hasProperty"""
419 return hasattr(self.__underlying__, name)
420
421 security.declarePrivate("setProperty")
422 def setProperty(self, name, value):
423 """setProperty => Try to set the property...
424 By now, it's available only for LDAPUserFolder
425 """
426 # Get actual source
427 src = self._GRUF.getUserSource(self.getUserSourceId())
428 if not src:
429 raise RuntimeError, "Invalid or missing user source for '%s'." % (self.getId(),)
430
431 # LDAPUserFolder => specific API.
432 if hasattr(src, "manage_setUserProperty"):
433 # Unmap pty name if necessary, get it in the schema
434 ldapname = None
435 for schema in src.getSchemaConfig().values():
436 if schema["ldap_name"] == name:
437 ldapname = schema["ldap_name"]
438 if schema["public_name"] == name:
439 ldapname = schema["ldap_name"]
440 break
441
442 # If we didn't find it, we skip it
443 if ldapname is None:
444 raise KeyError, "Invalid LDAP attribute: '%s'." % (name, )
445
446 # Edit user
447 user_dn = src._find_user_dn(self.getUserName())
448 src.manage_setUserProperty(user_dn, ldapname, value)
449
450 # Expire the underlying user object
451 self.__underlying__ = src.getUser(self.getId())
452 if not self.__underlying__:
453 raise RuntimeError, "Error while setting property of '%s'." % (self.getId(),)
454
455 # Now we check if the property has been changed
456 if not self.hasProperty(name):
457 raise NotImplementedError, "Property setting is not supported for '%s'." % (name,)
458 v = self._GRUF.getUserById(self.getId()).getProperty(name)
459 if not v == value:
460 Log(LOG_DEBUG, "Property '%s' for user '%s' should be '%s' and not '%s'" % (
461 name, self.getId(), value, v,
462 ))
463 raise NotImplementedError, "Property setting is not supported for '%s'." % (name,)
464
465 # ------------------------------
466 # Internal User object interface
467 # ------------------------------
468
469 security.declarePrivate('authenticate')
470 def authenticate(self, password, request):
471 # We prevent groups from authenticating
472 if self._isGroup:
473 return None
474 return self.__underlying__.authenticate(password, request)
475
476
477 security.declarePublic('allowed')
478 def allowed(self, object, object_roles=None):
479 """Check whether the user has access to object. The user must
480 have one of the roles in object_roles to allow access."""
481
482 if object_roles is _what_not_even_god_should_do:
483 return 0
484
485 # Short-circuit the common case of anonymous access.
486 if object_roles is None or 'Anonymous' in object_roles:
487 return 1
488
489 # Provide short-cut access if object is protected by 'Authenticated'
490 # role and user is not nobody
491 if 'Authenticated' in object_roles and \
492 (self.getUserName() != 'Anonymous User'):
493 return 1
494
495 # Check for ancient role data up front, convert if found.
496 # This should almost never happen, and should probably be
497 # deprecated at some point.
498 if 'Shared' in object_roles:
499 object_roles = self._shared_roles(object)
500 if object_roles is None or 'Anonymous' in object_roles:
501 return 1
502
503
504 # Trying to make some speed improvements, changes starts here.
505 # Helge Tesdal, Plone Solutions AS, http://www.plonesolutions.com
506 # We avoid using the getRoles() and getRolesInContext() methods to be able
507 # to short circuit.
508
509 # Dict for faster lookup and avoiding duplicates
510 object_roles_dict = {}
511 for role in object_roles:
512 object_roles_dict[role] = 1
513
514 if [role for role in self.getUserRoles() if object_roles_dict.has_key(role)]:
515 if self._check_context(object):
516 return 1
517 return None
518
519 # Try the top level group roles.
520 if [role for role in self.getGroupRoles() if object_roles_dict.has_key(role)]:
521 if self._check_context(object):
522 return 1
523 return None
524
525 user_groups = self.getGroups()
526 # No luck on the top level, try local roles
527 inner_obj = getattr(object, 'aq_inner', object)
528 userid = self.getId()
529 while 1:
530 local_roles = getattr(inner_obj, '__ac_local_roles__', None)
531 if local_roles:
532 if callable(local_roles):
533 local_roles = local_roles()
534 dict = local_roles or {}
535
536 if [role for role in dict.get(userid, []) if object_roles_dict.has_key(role)]:
537 if self._check_context(object):
538 return 1
539 return None
540
541 # Get roles & local roles for groups
542 # This handles nested groups as well
543 for groupid in user_groups:
544 if [role for role in dict.get(groupid, []) if object_roles_dict.has_key(role)]:
545 if self._check_context(object):
546 return 1
547 return None
548
549 # LocalRole blocking
550 obj = getattr(inner_obj, 'aq_base', inner_obj)
551 if getattr(obj, '__ac_local_roles_block__', None):
552 break
553
554 # Loop control
555 inner = getattr(inner_obj, 'aq_inner', inner_obj)
556 parent = getattr(inner, 'aq_parent', None)
557 if parent is not None:
558 inner_obj = parent
559 continue
560 if hasattr(inner_obj, 'im_self'):
561 inner_obj=inner_obj.im_self
562 inner_obj=getattr(inner_obj, 'aq_inner', inner_obj)
563 continue
564 break
565 return None
566
567
568 security.declarePublic('hasRole')
569 def hasRole(self, *args, **kw):
570 """hasRole is an alias for 'allowed' and has been deprecated.
571
572 Code still using this method should convert to either 'has_role' or
573 'allowed', depending on the intended behaviour.
574
575 """
576 import warnings
577 warnings.warn('BasicUser.hasRole is deprecated, please use '
578 'BasicUser.allowed instead; hasRole was an alias for allowed, but '
579 'you may have ment to use has_role.', DeprecationWarning)
580 return self.allowed(*args, **kw)
581
582 # #
583 # Underlying user object support #
584 # #
585
586 def __getattr__(self, name):
587 # This will call the underlying object's methods
588 # if they are not found in this user object.
589 # We will have to check Chris' http://www.plope.com/Members/chrism/plone_on_zope_head
590 # to make it work with Zope HEAD.
591 ret = getattr(self.__dict__['__underlying__'], name)
592 return ret
593
594 security.declarePublic('getUnwrappedUser')
595 def getUnwrappedUser(self,):
596 """
597 same as GRUF.getUnwrappedUser, but implicitly with this particular user
598 """
599 return self.__dict__['__underlying__']
600
601 def __getitem__(self, name):
602 # This will call the underlying object's methods
603 # if they are not found in this user object.
604 return self.__underlying__[name]
605
606 # #
607 # HTML link support #
608 # #
609
610 def asHTML(self, implicit=0):
611 """
612 asHTML(self, implicit=0) => HTML string
613 Used to generate homogeneous links for management screens
614 """
615 acl_users = self.acl_users
616 if self.isGroup():
617 color = acl_users.group_color
618 kind = "Group"
619 else:
620 color = acl_users.user_color
621 kind = "User"
622
623 ret = '''<a href="%(href)s" alt="%(alt)s"><font color="%(color)s">%(name)s</font></a>''' % {
624 "color": color,
625 "href": "%s/%s/manage_workspace?FORCE_USER=1" % (acl_users.absolute_url(), self.getId(), ),
626 "name": self.getUserNameWithoutGroupPrefix(),
627 "alt": "%s (%s)" % (self.getUserNameWithoutGroupPrefix(), kind, ),
628 }
629 if implicit:
630 return "<i>%s</i>" % ret
631 return ret
632
633
634 security.declarePrivate("isInGroup")
635 def isInGroup(self, groupid):
636 """Return true if the user is member of the specified group id
637 (including transitive groups)"""
638 return groupid in self.getAllGroupIds()
639
640 security.declarePublic("getRealId")
641 def getRealId(self,):
642 """Return id WITHOUT group prefix
643 """
644 raise NotImplementedError, "Must be derived in subclasses"
645
646
647 class GRUFUser(GRUFUserAtom):
648 """
649 This is the class for actual user objects
650 """
651 __implements__ = (IUser, )
652
653 security = ClassSecurityInfo()
654
655 # #
656 # User Mutation #
657 # #
658
659 security.declarePublic('changePassword')
660 def changePassword(self, password, REQUEST=None):
661 """Set the user's password. This method performs its own security checks"""
662 # Check security
663 user = getSecurityManager().getUser()
664 if not user.has_permission(Permissions.manage_users, self._GRUF): # Is manager ?
665 if user.__class__.__name__ != "GRUFUser":
666 raise "Unauthorized", "You cannot change someone else's password."
667 if not user.getId() == self.getId(): # Is myself ?
668 raise "Unauthorized", "You cannot change someone else's password."
669
670 # Just do it
671 self.clearCachedGroupsAndRoles()
672 return self._GRUF.userSetPassword(self.getId(), password)
673 changePassword = postonly(changePassword)
674
675 security.declarePrivate("setRoles")
676 def setRoles(self, roles):
677 """Change the roles of a user atom.
678 """
679 self.clearCachedGroupsAndRoles()
680 return self._GRUF.userSetRoles(self.getId(), roles)
681
682 security.declarePrivate("addRole")
683 def addRole(self, role):
684 """Append a role for a user atom
685 """
686 self.clearCachedGroupsAndRoles()
687 return self._GRUF.userAddRole(self.getId(), role)
688
689 security.declarePrivate("removeRole")
690 def removeRole(self, role):
691 """Remove the role of a user atom
692 """
693 self.clearCachedGroupsAndRoles()
694 return self._GRUF.userRemoveRole(self.getId(), role)
695
696 security.declarePrivate("setPassword")
697 def setPassword(self, newPassword):
698 """Set the password of a user
699 """
700 self.clearCachedGroupsAndRoles()
701 return self._GRUF.userSetPassword(self.getId(), newPassword)
702
703 security.declarePrivate("setDomains")
704 def setDomains(self, domains):
705 """Set domains for a user
706 """
707 self.clearCachedGroupsAndRoles()
708 self._GRUF.userSetDomains(self.getId(), domains)
709 self._original_domains = self._GRUF.userGetDomains(self.getId())
710
711 security.declarePrivate("addDomain")
712 def addDomain(self, domain):
713 """Append a domain to a user
714 """
715 self.clearCachedGroupsAndRoles()
716 self._GRUF.userAddDomain(self.getId(), domain)
717 self._original_domains = self._GRUF.userGetDomains(self.getId())
718
719 security.declarePrivate("removeDomain")
720 def removeDomain(self, domain):
721 """Remove a domain from a user
722 """
723 self.clearCachedGroupsAndRoles()
724 self._GRUF.userRemoveDomain(self.getId(), domain)
725 self._original_domains = self._GRUF.userGetDomains(self.getId())
726
727 security.declarePrivate("setGroups")
728 def setGroups(self, groupnames):
729 """Set the groups of a user
730 """
731 self.clearCachedGroupsAndRoles()
732 return self._GRUF.userSetGroups(self.getId(), groupnames)
733
734 security.declarePrivate("addGroup")
735 def addGroup(self, groupname):
736 """add a group to a user atom
737 """
738 self.clearCachedGroupsAndRoles()
739 return self._GRUF.userAddGroup(self.getId(), groupname)
740
741 security.declarePrivate("removeGroup")
742 def removeGroup(self, groupname):
743 """remove a group from a user atom.
744 """
745 self.clearCachedGroupsAndRoles()
746 return self._GRUF.userRemoveGroup(self.getId(), groupname)
747
748 security.declarePrivate('_getPassword')
749 def _getPassword(self):
750 """Return the password of the user."""
751 return self._original_password
752
753 security.declarePublic("getRealId")
754 def getRealId(self,):
755 """Return id WITHOUT group prefix
756 """
757 return self.getId()
758
759
760 class GRUFGroup(GRUFUserAtom):
761 """
762 This is the class for actual group objects
763 """
764 __implements__ = (IGroup, )
765
766 security = ClassSecurityInfo()
767
768 security.declarePublic("getRealId")
769 def getRealId(self,):
770 """Return group id WITHOUT group prefix
771 """
772 return self.getId()[len(GROUP_PREFIX):]
773
774 def _getLDAPMemberIds(self,):
775 """
776 _getLDAPMemberIds(self,) => Uses LDAPUserFolder to find
777 users in a group.
778 """
779 # Find the right source
780 gruf = self.aq_parent
781 src = None
782 for src in gruf.listUserSources():
783 if not src.meta_type == "LDAPUserFolder":
784 continue
785 if src is None:
786 Log(LOG_DEBUG, "No LDAPUserFolder source found")
787 return []
788
789 # Find the group in LDAP
790 groups = src.getGroups()
791 groupid = self.getId()
792 grp = [ group for group in groups if group[0] == self.getId() ]
793 if not grp:
794 Log(LOG_DEBUG, "No such group ('%s') found." % (groupid,))
795 return []
796
797 # Return the grup member ids
798 userids = src.getGroupedUsers(grp)
799 Log(LOG_DEBUG, "We've found %d users belonging to the group '%s'" % (len(userids), grp), )
800 return userids
801
802 def _getMemberIds(self, users = 1, groups = 1, transitive = 1, ):
803 """
804 Return the member ids (users and groups) of the atoms of this group.
805 Transitiveness attribute is ignored with LDAP (no nested groups with
806 LDAP anyway).
807 This method now uses a shortcut to fetch members of an LDAP group
808 (stored either within Zope or within your LDAP server)
809 """
810 # Initial parameters.
811 # We fetch the users/groups list depending on what we search,
812 # and carefuly avoiding to use LDAP sources.
813 gruf = self.aq_parent
814 ldap_sources = []
815 lst = []
816 if transitive:
817 method = "getAllGroupIds"
818 else:
819 method = "getGroupIds"
820 if users:
821 for src in gruf.listUserSources():
822 if src.meta_type == 'LDAPUserFolder':
823 ldap_sources.append(src)
824 continue # We'll fetch 'em later
825 lst.extend(src.getUserNames())
826 if groups:
827 lst.extend(gruf.getGroupIds())
828
829 # First extraction for regular user sources.
830 # This part is very very long, and the more users you have,
831 # the longer this method will be.
832 groupid = self.getId()
833 groups_mapping = {}
834 for u in lst:
835 usr = gruf.getUser(u)
836 if not usr:
837 groups_mapping[u] = []
838 Log(LOG_WARNING, "Invalid user retreiving:", u)
839 else:
840 groups_mapping[u] = getattr(usr, method)()
841 members = [u for u in lst if groupid in groups_mapping[u]]
842
843 # If we have LDAP sources, we fetch user-group mapping inside directly
844 groupid = self.getId()
845 for src in ldap_sources:
846 groups = src.getGroups()
847 # With LDAPUserFolder >= 2.7 we need to add GROUP_PREFIX to group_name
848 # We keep backward compatibility
849 grp = [ group for group in groups if group[0] == self.getId() or \
850 GROUP_PREFIX + group[0] == self.getId()]
851 if not grp:
852 Log(LOG_DEBUG, "No such group ('%s') found." % (groupid,))
853 continue
854
855 # Return the grup member ids
856 userids = [ str(u) for u in src.getGroupedUsers(grp) ]
857 Log(LOG_DEBUG, "We've found %d users belonging to the group '%s'" % (len(userids), grp), )
858 members.extend(userids)
859
860 # Return the members we've found
861 return members
862
863 security.declarePrivate("getMemberIds")
864 def getMemberIds(self, transitive = 1, ):
865 "Return member ids of this group, including or not transitive groups."
866 return self._getMemberIds(transitive = transitive)
867
868 security.declarePrivate("getUserMemberIds")
869 def getUserMemberIds(self, transitive = 1, ):
870 """Return the member ids (users only) of the users of this group"""
871 return self._getMemberIds(groups = 0, transitive = transitive)
872
873 security.declarePrivate("getGroupMemberIds")
874 def getGroupMemberIds(self, transitive = 1, ):
875 """Return the members ids (groups only) of the groups of this group"""
876 return self._getMemberIds(users = 0, transitive = transitive)
877
878 security.declarePrivate("hasMember")
879 def hasMember(self, id):
880 """Return true if the specified atom id is in the group.
881 This is the contrary of IUserAtom.isInGroup(groupid)"""
882 gruf = self.aq_parent
883 return id in gruf.getMemberIds(self.getId())
884
885 security.declarePrivate("addMember")
886 def addMember(self, userid):
887 """Add a user the the current group"""
888 gruf = self.aq_parent
889 groupid = self.getId()
890 usr = gruf.getUser(userid)
891 if not usr:
892 raise ValueError, "Invalid user: '%s'" % (userid, )
893 if not groupid in gruf.getGroupNames() + gruf.getGroupIds():
894 raise ValueError, "Invalid group: '%s'" % (groupid, )
895 groups = list(usr.getGroups())
896 groups.append(groupid)
897 groups = GroupUserFolder.unique(groups)
898 return gruf._updateUser(userid, groups = groups)
899
900 security.declarePrivate("removeMember")
901 def removeMember(self, userid):
902 """Remove a user from the current group"""
903 gruf = self.aq_parent
904 groupid = self.getId()
905
906 # Check the user
907 usr = gruf.getUser(userid)
908 if not usr:
909 raise ValueError, "Invalid user: '%s'" % (userid, )
910
911 # Now, remove the group
912 groups = list(usr.getImmediateGroups())
913 if groupid in groups:
914 groups.remove(groupid)
915 gruf._updateUser(userid, groups = groups)
916 else:
917 raise ValueError, "User '%s' doesn't belong to group '%s'" % (userid, groupid, )
918
919 security.declarePrivate("setMembers")
920 def setMembers(self, userids):
921 """Set the members of the group
922 """
923 member_ids = self.getMemberIds()
924 all_ids = copy(member_ids)
925 all_ids.extend(userids)
926 groupid = self.getId()
927 for id in all_ids:
928 if id in member_ids and id not in userids:
929 self.removeMember(id)
930 elif id not in member_ids and id in userids:
931 self.addMember(id)
932
933
934 InitializeClass(GRUFUser)
935 InitializeClass(GRUFGroup)