1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # Copyright (C) 2005-2007 BenoƮt PIN <benoit.pin@ensmp.fr> #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Plinn portal_membership
26 from AccessControl
import ClassSecurityInfo
, getSecurityManager
27 from AccessControl
.unauthorized
import Unauthorized
28 from AccessControl
.SpecialUsers
import nobody
29 from AccessControl
.Permission
import Permission
30 from Acquisition
import aq_base
, aq_inner
31 from Globals
import InitializeClass
, MessageDialog
32 from Products
.PageTemplates
.PageTemplateFile
import PageTemplateFile
34 from Products
.CMFDefault
.MembershipTool
import MembershipTool
as BaseTool
35 from Products
.CMFCore
.permissions
import View
, ListPortalMembers
, ManagePortal
, SetOwnPassword
, ChangePermissions
36 from permissions
import RemoveMember
, SetLocalRoles
, CheckMemberPermission
37 from utils
import _checkMemberPermission
38 from Products
.CMFCore
.utils
import _checkPermission
, _getAuthenticatedUser
39 from Products
.CMFCore
.utils
import getUtilityByInterfaceName
40 from utils
import formatFullName
, translate
41 from Products
.CMFDefault
.utils
import decode
42 from Products
.CMFDefault
.Document
import addDocument
45 from types
import TupleType
49 from logging
import getLogger
50 console
= getLogger('Plinn.MembershipTool')
53 class MembershipTool( BaseTool
):
54 """ Implement 'portal_membership' interface using "stock" policies.
58 meta_type
= 'Plinn Membership Tool'
60 manage_options
=( ({ 'label' : 'Configuration'
61 , 'action' : 'manage_mapRoles'
62 },) + BaseTool
.manage_options
[1:])
64 security
= ClassSecurityInfo()
66 security
.declareProtected(ManagePortal
, 'manage_mapRoles')
67 manage_mapRoles
= PageTemplateFile('www/configureMembershipTool', globals(),
68 __name__
='manage_mapRoles')
71 # 'portal_membership' interface methods
74 # change security settings for inherited methods
75 security
.declareProtected(ListPortalMembers
, 'getMemberById')
78 memberareaPortalType
= 'Huge Plinn Folder'
81 # security.declareProtected(SetOwnPassword, 'setPassword')
82 # def setPassword(self, password, domains=None):
83 # '''Allows the authenticated member to set his/her own password.
85 # user_folder = self.__getPUS()
86 # if user_folder.meta_type == 'Group User Folder' :
87 # registration = getToolByName(self, 'portal_registration', None)
88 # if not self.isAnonymousUser():
89 # member = self.getAuthenticatedMember()
91 # failMessage = registration.testPasswordValidity(password)
92 # if failMessage is not None:
93 # raise 'Bad Request', failMessage
94 # member.setSecurityProfile(password=password, domains=domains)
95 # member.changePassword(password)
97 # raise 'Bad Request', 'Not logged in.'
100 # BaseTool.setPassword(self, password, domains=None)
104 security
.declareProtected(ListPortalMembers
, 'listMemberIds')
105 def listMemberIds(self
):
106 '''Lists the ids of all members. This may eventually be
107 replaced with a set of methods for querying pieces of the
108 list rather than the entire list at once.
110 user_folder
= self
.__getPUS
()
111 if user_folder
.meta_type
== 'Group User Folder' :
112 return user_folder
.getPureUserNames()
114 return [ x
.getId() for x
in user_folder
.getUsers() ]
117 security
.declareProtected(CheckMemberPermission
, 'checkMemberPermission')
118 def checkMemberPermission(self
, userid
, permissionName
, object, subobjectName
=None):
120 Checks whether the current user has the given permission on
121 the given object or subobject.
123 if subobjectName
is not None:
124 object = getattr(object, subobjectName
)
126 return _checkMemberPermission(userid
, permissionName
, object)
128 security
.declareProtected(ListPortalMembers
, 'listMembers')
129 def listMembers(self
):
130 '''Gets the list of all members.
132 user_folder
= self
.__getPUS
()
133 if user_folder
.meta_type
== 'Group User Folder' :
134 return map(self
.wrapUser
, user_folder
.getPureUsers())
136 return map(self
.wrapUser
, user_folder
.getUsers())
139 security
.declareProtected(View
, 'getCandidateLocalRoles')
140 def getCandidateLocalRoles(self
, obj
) :
141 """ What local roles can I assign?
143 member
= self
.getAuthenticatedMember()
144 valid_roles
= obj
.valid_roles()
145 if 'Manager' in member
.getRoles():
146 local_roles
= [r
for r
in valid_roles
if r
!= 'Anonymous']
148 sm
= getSecurityManager()
149 allPermissions
= self
.ac_inherited_permissions(1)
151 # construct a dictionary of permissions indexed by role
152 # and get permissions of user in obj context
153 memberPermissions
= Set()
155 for role
in valid_roles
:
156 rolesMappings
[role
] = Set()
158 for p
in allPermissions
:
161 p
=Permission(name
,value
,obj
)
162 rolesOfPerm
= p
.getRoles()
164 for role
in rolesOfPerm
:
165 try : rolesMappings
[role
].add(name
)
168 if hasattr(obj
, trName
):
169 l
= list(getattr(obj
, trName
))
171 setattr(obj
, trName
, tuple(l
))
172 msg
= '%s role has been removed for %s permission on %s ' % (role
, name
, obj
.absolute_url())
173 #LOG('portal_membership', WARNING, msg)
175 parent
= obj
.aq_inner
.aq_parent
176 while type(rolesOfPerm
) != TupleType
:
177 p
=Permission(name
, value
, parent
)
178 rolesOfPerm
= p
.getRoles()
179 for role
in rolesOfPerm
:
180 try : rolesMappings
[role
].add(name
)
181 except KeyError : pass
182 try : parent
= parent
.aq_inner
.aq_parent
183 except AttributeError : break
186 if sm
.checkPermission(name
, obj
) :
187 memberPermissions
.add(name
)
190 for role
in valid_roles
:
191 if rolesMappings
[role
] and rolesMappings
[role
].issubset(memberPermissions
) :
192 local_roles
.append(role
)
194 local_roles
= [ role
for role
in local_roles
if role
not in ('Shared', 'Authenticated', 'Member', 'Anonymous') ]
196 return tuple(local_roles
)
199 security
.declareProtected(View
, 'setLocalRoles')
200 def setLocalRoles( self
, obj
, member_ids
, role
, remove
=0, reindex
=1 ):
201 """ Set local roles on an item """
202 if role
not in self
.getCandidateLocalRoles(obj
) :
203 raise Unauthorized
, "You are not allowed to manage %s role" % role
205 if self
.checkPermission(SetLocalRoles
, obj
) :
207 for member_id
in member_ids
:
208 # current roles for user id in obj
209 roles
= list(obj
.get_local_roles_for_userid( userid
=member_id
))
210 if role
not in roles
:
212 obj
.manage_setLocalRoles( member_id
, roles
)
214 for member_id
in member_ids
:
215 # current roles for user id in obj
216 roles
= list(obj
.get_local_roles_for_userid( userid
=member_id
))
217 try : roles
.remove(role
)
218 except ValueError : pass
221 obj
.manage_setLocalRoles( member_id
, roles
)
223 obj
.manage_delLocalRoles( userids
=[member_id
] )
229 # It is assumed that all objects have the method
230 # reindexObjectSecurity, which is in CMFCatalogAware and
231 # thus PortalContent and PortalFolder.
232 obj
.reindexObjectSecurity()
235 security
.declarePublic('getMemberFullNameById')
236 def getMemberFullNameById(self
, userid
, nameBefore
= 1) :
237 """ Return the best formated representation of user fullname. """
240 if userid
and userid
!= 'No owner' :
241 # No owner is a possible value returned by DefaultDublinCoreImpl.Creator
242 member
= self
.getMemberById(userid
)
245 memberFullName
= member
.getMemberFullName(nameBefore
=nameBefore
)
247 return memberFullName
249 security
.declareProtected(ListPortalMembers
, 'getMembers')
250 def getMembers(self
, users
) :
251 """ Return wraped users """
254 members
.append(self
.getMemberById(user
))
256 members
= filter(None, members
)
257 members
.sort( lambda m0
, m1
: cmp(m0
.getMemberSortableFormat(), m1
.getMemberSortableFormat()) )
261 security
.declareProtected(ListPortalMembers
, 'getOtherMembers')
262 def getOtherMembers(self
, users
) :
263 """ Return members who are not in users list"""
264 allMemberIds
= self
.listMemberIds()
265 otherMemberIds
= [ userId
for userId
in allMemberIds
if userId
not in users
]
266 return self
.getMembers(otherMemberIds
)
270 security
.declareProtected(ListPortalMembers
, 'getMembersMetadata')
271 def getMembersMetadata(self
, users
) :
272 """ return metadata from portal_catalog """
274 for u
in users
: userDict
[u
] = True
275 ctool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.ICatalogTool')
276 memberBrains
= ctool(portal_type
='Member Data', sort_on
='getMemberSortableFormat')
281 for mb
in memberBrains
:
282 metadata
= {'id' : mb
.getId
, 'fullname' : mb
.getMemberFullName
}
283 if userDict
.has_key(mb
.getId
) :
284 memberList
.append(metadata
)
286 complementList
.append(metadata
)
288 complementList
= [{'id' : mb
.getId
, 'fullname' : mb
.getMemberFullName
} for mb
in memberBrains
]
290 return {'memberList' : memberList
, 'complementList' : complementList
}
294 security
.declareProtected(RemoveMember
, 'removeMembers')
295 def removeMembers(self
, memberIds
= []) :
298 # TODO : remove member document ?
299 mdtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMemberDataTool')
300 for m
in self
.getMembers(memberIds
) :
301 m
.manage_beforeDelete()
302 mdtool
.deleteMemberData(m
.getId())
304 self
.aq_inner
.acl_users
.deleteUsers(users
= memberIds
)
308 security
.declareProtected(ManagePortal
, 'setMemberAreaPortalType')
309 def setMemberAreaPortalType(self
, member_folder_portal_type
):
310 """ Set member area portal type to construct."""
311 ttool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.ITypesTool')
312 if member_folder_portal_type
not in ttool
.objectIds() :
313 raise ValueError, "Unknown portal type : %s" % str(member_folder_portal_type
)
315 self
.memberareaPortalType
= member_folder_portal_type
316 return MessageDialog(title
='Type updated',
317 message
='The member area type have been updated',
318 action
='manage_mapRoles')
320 def getMemberAreaPortalType(self
) :
321 return self
.memberareaPortalType
324 def getHomeFolder(self
, id=None, verifyPermission
=0):
325 """ Return a member's home folder object, or None.
328 member
= self
.getAuthenticatedMember()
329 if not hasattr(member
, 'getMemberId'):
331 id = member
.getMemberId()
332 members
= self
.getMembersFolder()
333 if members
is not None:
334 if not hasattr(members
, id) and getattr(self
, 'memberareaCreationFlag', 0) != 0 :
335 self
.createMemberArea(id)
337 folder
= members
._getOb
(id)
338 if verifyPermission
and not _checkPermission(View
, folder
):
339 # Don't return the folder if the user can't get to it.
342 except (AttributeError, TypeError, KeyError):
346 security
.declarePublic('createMemberArea')
347 def createMemberArea(self
, member_id
=''):
348 """ Create a member area for 'member_id' or authenticated user.
350 if not self
.getMemberareaCreationFlag():
352 members
= self
.getMembersFolder()
355 if self
.isAnonymousUser():
357 # Note: We can't use getAuthenticatedMember() and getMemberById()
358 # because they might be wrapped by MemberDataTool.
359 user
= _getAuthenticatedUser(self
)
360 user_id
= user
.getId()
361 if member_id
in ('', user_id
):
365 if _checkPermission(ManageUsers
, self
):
366 member
= self
.acl_users
.getUserById(member_id
, None)
368 member
= member
.__of
__(self
.acl_users
)
370 raise ValueError, 'Member %s does not exist' % member_id
374 if hasattr( aq_base(members
), member_id
):
377 ttool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.ITypesTool')
378 info
= getattr(ttool
, self
.memberareaPortalType
)
380 memberFullName
= self
.getMemberFullNameById(member_id
, nameBefore
= 0)
381 f
= info
._constructInstance
( members
, member_id
, title
=memberFullName
)
383 # Grant Ownership and Owner role to Member
384 f
.changeOwnership(user
)
385 f
.__ac
_local
_roles
__ = None
386 f
.manage_setLocalRoles(member_id
, ['Owner'])
388 f
.reindexObjectSecurity()
390 # Create Member's initial content.
391 if hasattr(self
, 'createMemberContent') :
392 self
.createMemberContent(member
=user
,
396 def _(message
, context
, expand
=()) :
397 trmessage
= decode(translate(message
, context
), context
)
398 expand
= tuple([decode(e
, context
) for e
in expand
])
399 return (trmessage
% expand
).encode('utf-8')
401 # Create Member's home page.
404 , title
= _("%s's Home", self
, (memberFullName
,))
405 , description
= _("%s's front page", self
, (memberFullName
,))
406 , text_format
= "html"
407 , text
= self
.default_member_content(memberFullName
=memberFullName
).encode('utf-8')
410 # Grant Ownership and Owner role to Member
411 f
.index_html
.changeOwnership(user
)
412 f
.index_html
.__ac
_local
_roles
__ = None
413 f
.index_html
.manage_setLocalRoles(member_id
, ['Owner'])
415 f
.index_html
._setPortalTypeName
( 'Document' )
417 # Overcome an apparent catalog bug.
418 f
.index_html
.reindexObject()
419 wftool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IWorkflowTool')
420 wftool
.notifyCreated( f
.index_html
)
425 security
.declareProtected(ListPortalMembers
, 'looseSearchMembers')
426 def looseSearchMembers(self
, searchString
) :
429 words
= searchString
.strip().split()
430 words
= [word
.lower() for word
in words
]
432 mdtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMemberDataTool')
433 mdProperties
= mdtool
.propertyIds()
434 searchableProperties
= [ p
['id'] for p
in mdtool
.propertyMap() if p
['type'] == 'string' ] + ['id']
435 try : searchableProperties
.remove('portal_skin')
436 except ValueError : pass
439 for m
in self
.listMembers() :
440 allWordsMatch
= False
442 for p
in searchableProperties
:
443 if str(m
.getProperty(p
, '')).lower().find(word
) != -1 :
447 allWordsMatch
= False
449 if not allWordsMatch
:
457 # CMFCore.MembershipTool.MembershipTool tests 'getUsers' method but :
458 # "enumeration" methods ('getUserNames', 'getUsers') are *not*
459 # part of the contract! See IEnumerableUserFolder.
460 # (from PluggableAuthService.interfaces.authservice #233)
461 return self
.acl_users
464 InitializeClass(MembershipTool
)