[gen] Get rid of some Zope-specific security aspects. This is a preparatory work to extend the Appy authentication system to perform ldap authentication as well.
This commit is contained in:
parent
04852360fa
commit
5223af2a62
14 changed files with 198 additions and 450 deletions
|
@ -30,55 +30,18 @@ class GroupWrapper(AbstractWrapper):
|
|||
'''Inter-field validation.'''
|
||||
return self._callCustom('validate', new, errors)
|
||||
|
||||
def confirm(self, new):
|
||||
'''Use this method for remembering the previous list of users for this
|
||||
group.'''
|
||||
obj = self.o
|
||||
if hasattr(obj.aq_base, '_oldUsers'): del obj.aq_base._oldUsers
|
||||
obj._oldUsers = self.users
|
||||
|
||||
def addUser(self, user):
|
||||
'''Adds a p_user to this group.'''
|
||||
# Update the Ref field.
|
||||
self.link('users', user)
|
||||
# Update the group-related info on the Zope user.
|
||||
zopeUser = user.getZopeUser()
|
||||
zopeUser.groups[self.login] = self.roles
|
||||
|
||||
def removeUser(self, user):
|
||||
'''Removes a p_user from this group.'''
|
||||
self.unlink('users', user)
|
||||
# Update the group-related info on the Zope user.
|
||||
zopeUser = user.getZopeUser()
|
||||
del zopeUser.groups[self.login]
|
||||
|
||||
def onEdit(self, created):
|
||||
# Create or update, on every Zope user of this group, group-related
|
||||
# information.
|
||||
# 1. Remove reference to this group for users that were removed from it
|
||||
newUsers = self.users
|
||||
# The list of previously existing users does not exist when editing a
|
||||
# group from Python. For updating self.users, it is recommended to use
|
||||
# methods m_addUser and m_removeUser above.
|
||||
oldUsers = getattr(self.o.aq_base, '_oldUsers', ())
|
||||
for user in oldUsers:
|
||||
if user not in newUsers:
|
||||
del user.getZopeUser().groups[self.login]
|
||||
self.log('User "%s" removed from group "%s".' % \
|
||||
(user.login, self.login))
|
||||
# 2. Add reference to this group for users that were added to it
|
||||
for user in newUsers:
|
||||
zopeUser = user.getZopeUser()
|
||||
# We refresh group-related info on the Zope user even if the user
|
||||
# was already in the group.
|
||||
zopeUser.groups[self.login] = self.roles
|
||||
if user not in oldUsers:
|
||||
self.log('User "%s" added to group "%s".' % \
|
||||
(user.login, self.login))
|
||||
if hasattr(self.o.aq_base, '_oldUsers'): del self.o._oldUsers
|
||||
# If the group was created by an Anonymous, Anonymous can't stay Owner
|
||||
# of the object.
|
||||
if None in self.o.__ac_local_roles__:
|
||||
del self.o.__ac_local_roles__[None]
|
||||
# If the group was created by anon, anon can't stay its Owner.
|
||||
if 'anon' in self.o.__ac_local_roles__:
|
||||
del self.o.__ac_local_roles__['anon']
|
||||
return self._callCustom('onEdit', created)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -174,8 +174,8 @@ class ToolWrapper(AbstractWrapper):
|
|||
</table>
|
||||
|
||||
<!-- One section for every searchable root class -->
|
||||
<x for="rootClass in [rc for rc in rootClasses \
|
||||
if ztool.userMaySearch(rc)]">
|
||||
<x for="rootClass in rootClasses" if="ztool.userMaySearch(rootClass)"
|
||||
var2="className=ztool.getPortalType(rootClass)">
|
||||
|
||||
<!-- A separator if required -->
|
||||
<div class="portletSep" var="nb=loop.rootClass.nb"
|
||||
|
@ -188,31 +188,30 @@ class ToolWrapper(AbstractWrapper):
|
|||
<a var="queryParam=searchInfo.default and \
|
||||
searchInfo.default.name or ''"
|
||||
href=":'%s?className=%s&search=%s' % \
|
||||
(queryUrl,rootClass,queryParam)"
|
||||
class=":(not currentSearch and (currentClass==rootClass) and \
|
||||
(queryUrl, className, queryParam)"
|
||||
class=":(not currentSearch and (currentClass==className) and \
|
||||
(currentPage=='query')) and \
|
||||
'portletCurrent' or ''">::_(rootClass + '_plural')</a>
|
||||
'portletCurrent' or ''">::_(className + '_plural')</a>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<x var="addPermission='%s: Add %s' % (appName, rootClass);
|
||||
userMayAdd=user.has_permission(addPermission, appFolder);
|
||||
<x var="mayCreate=ztool.userMayCreate(rootClass);
|
||||
createMeans=ztool.getCreateMeans(rootClass)">
|
||||
|
||||
<!-- Create a new object from a web form -->
|
||||
<!-- Create a new object from a web form. -->
|
||||
<input type="button" class="button"
|
||||
if="userMayAdd and ('form' in createMeans)"
|
||||
if="mayCreate and ('form' in createMeans)"
|
||||
style=":url('buttonAdd', bg=True)" value=":_('query_create')"
|
||||
onclick=":'goto(%s)' % \
|
||||
q('%s/do?action=Create&className=%s' % \
|
||||
(toolUrl, rootClass))"/>
|
||||
(toolUrl, className))"/>
|
||||
|
||||
<!-- Create object(s) by importing data -->
|
||||
<input type="button" class="button"
|
||||
if="userMayAdd and ('import' in createMeans)"
|
||||
if="mayCreate and ('import' in createMeans)"
|
||||
style=":url('buttonImport', bg=True)" value=":_('query_import')"
|
||||
onclick=":'goto(%s)' % \
|
||||
q('%s/import?className=%s' % (toolUrl, rootClass))"/>
|
||||
q('%s/import?className=%s' % (toolUrl, className))"/>
|
||||
</x>
|
||||
|
||||
<!-- Searches -->
|
||||
|
@ -221,7 +220,7 @@ class ToolWrapper(AbstractWrapper):
|
|||
<!-- Live search -->
|
||||
<form action=":'%s/do' % toolUrl">
|
||||
<input type="hidden" name="action" value="SearchObjects"/>
|
||||
<input type="hidden" name="className" value=":rootClass"/>
|
||||
<input type="hidden" name="className" value=":className"/>
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
<tr valign="bottom">
|
||||
<td><input type="text" size="14" name="w_SearchableText"
|
||||
|
@ -234,13 +233,13 @@ class ToolWrapper(AbstractWrapper):
|
|||
</form>
|
||||
|
||||
<!-- Advanced search -->
|
||||
<div var="highlighted=(currentClass == rootClass) and \
|
||||
<div var="highlighted=(currentClass == className) and \
|
||||
(currentPage == 'search')"
|
||||
class=":highlighted and 'portletSearch portletCurrent' or \
|
||||
'portletSearch'"
|
||||
align=":dright">
|
||||
<a var="text=_('search_title')" style="font-size: 88%"
|
||||
href=":'%s/search?className=%s' % (toolUrl, rootClass)"
|
||||
href=":'%s/search?className=%s' % (toolUrl, className)"
|
||||
title=":text"><x>:text</x>...</a>
|
||||
</div>
|
||||
</x>
|
||||
|
@ -754,16 +753,6 @@ class ToolWrapper(AbstractWrapper):
|
|||
'''Sends a mail. See doc for appy.gen.mail.sendMail.'''
|
||||
sendMail(self, to, subject, body, attachments=attachments)
|
||||
|
||||
def refreshSecurity(self):
|
||||
'''Refreshes, on every object in the database, security-related,
|
||||
workflow-managed information.'''
|
||||
context = {'nb': 0}
|
||||
for className in self.o.getProductConfig().allClassNames:
|
||||
self.compute(className, context=context, noSecurity=True,
|
||||
expression="ctx['nb'] += int(obj.o.refreshSecurity())")
|
||||
msg = 'Security refresh: %d object(s) updated.' % context['nb']
|
||||
self.log(msg)
|
||||
|
||||
def refreshCatalog(self, startObject=None):
|
||||
'''Reindex all Appy objects. For some unknown reason, method
|
||||
catalog.refreshCatalog is not able to recatalog Appy objects.'''
|
||||
|
|
|
@ -140,8 +140,18 @@ class UserWrapper(AbstractWrapper):
|
|||
else:
|
||||
self.title = self.login
|
||||
|
||||
def ensureAdminIsManager(self):
|
||||
'''User 'admin' must always have role 'Manager'.'''
|
||||
if self.o.id == 'admin':
|
||||
roles = self.roles
|
||||
if 'Manager' not in roles:
|
||||
if not roles: roles = ['Manager']
|
||||
else: roles.append('Manager')
|
||||
self.roles = roles
|
||||
|
||||
def onEdit(self, created):
|
||||
self.updateTitle()
|
||||
self.ensureAdminIsManager()
|
||||
aclUsers = self.o.acl_users
|
||||
login = self.login
|
||||
if created:
|
||||
|
@ -173,19 +183,25 @@ class UserWrapper(AbstractWrapper):
|
|||
self.o.manage_addLocalRoles(login, ('Owner',))
|
||||
# If the user was created by an Anonymous, Anonymous can't stay Owner
|
||||
# of the object.
|
||||
if None in self.o.__ac_local_roles__:
|
||||
del self.o.__ac_local_roles__[None]
|
||||
if 'anon' in self.o.__ac_local_roles__:
|
||||
del self.o.__ac_local_roles__['anon']
|
||||
return self._callCustom('onEdit', created)
|
||||
|
||||
def mayEdit(self):
|
||||
'''No one can edit users "system" and "anon".'''
|
||||
if self.o.id in ('system', 'anon'): return
|
||||
# Call custom "mayEdit" when present.
|
||||
custom = self._getCustomMethod('mayEdit')
|
||||
if custom: return self._callCustom('mayEdit')
|
||||
else: return True
|
||||
return True
|
||||
|
||||
def mayDelete(self):
|
||||
'''No one can delete users "system", "anon" and "admin".'''
|
||||
if self.o.id in ('system', 'anon', 'admin'): return
|
||||
# Call custom "mayDelete" when present.
|
||||
custom = self._getCustomMethod('mayDelete')
|
||||
if custom: return self._callCustom('mayDelete')
|
||||
else: return True
|
||||
return True
|
||||
|
||||
def getZopeUser(self):
|
||||
'''Gets the Zope user corresponding to this user.'''
|
||||
|
@ -198,97 +214,87 @@ class UserWrapper(AbstractWrapper):
|
|||
# Call a custom "onDelete" if any.
|
||||
return self._callCustom('onDelete')
|
||||
|
||||
# Standard Zope user methods -----------------------------------------------
|
||||
def getLogins(self):
|
||||
'''Gets all the logins that can "match" this user: it own login and the
|
||||
logins of all the groups he belongs to.'''
|
||||
# Try first to get those logins from a cache on the request.
|
||||
try:
|
||||
return self.request.userLogins
|
||||
except AttributeError:
|
||||
res = [group.login for group in self.groups]
|
||||
res.append(self.login)
|
||||
return res
|
||||
|
||||
def getRoles(self):
|
||||
'''This method returns all the global roles for this user, not simply
|
||||
self.roles, but also "ungrantable roles" (like Anonymous or
|
||||
Authenticated) and roles inherited from group membership.'''
|
||||
# Try first to get those roles from a cache on the request.
|
||||
try:
|
||||
return self.request.userRoles
|
||||
except AttributeError:
|
||||
res = list(self.roles)
|
||||
# Add ungrantable roles
|
||||
if self.o.id == 'anon':
|
||||
res.append('Anonymous')
|
||||
else:
|
||||
res.append('Authenticated')
|
||||
# Add group global roles
|
||||
for group in self.groups:
|
||||
for role in group.roles:
|
||||
if role not in res: res.append(role)
|
||||
return res
|
||||
|
||||
def getRolesFor(self, obj):
|
||||
'''Gets the roles the user has in the context of p_obj: its global roles
|
||||
+ its roles which are local to p_obj.'''
|
||||
obj = obj.o
|
||||
# Start with user global roles.
|
||||
res = self.getRoles()
|
||||
# Add local roles, granted to the user directly or to one of its groups.
|
||||
localRoles = getattr(obj.aq_base, '__ac_local_roles__', None)
|
||||
if not localRoles: return res
|
||||
# Gets the logins of this user and all its groups.
|
||||
logins = self.getLogins()
|
||||
for login, roles in localRoles.iteritems():
|
||||
# Ignore logins not corresponding to this user.
|
||||
if login not in logins: continue
|
||||
for role in roles:
|
||||
if role not in res: res.append(role)
|
||||
return res
|
||||
|
||||
def has_role(self, role, obj=None):
|
||||
zopeUser = self.request.zopeUser
|
||||
if obj: return zopeUser.has_role(role, obj)
|
||||
return zopeUser.has_role(role)
|
||||
'''Has the logged user some p_role? If p_obj is None, check if the user
|
||||
has p_role globally; else, check if he has this p_role in the context
|
||||
of p_obj.'''
|
||||
if obj:
|
||||
roles = self.getRolesFor(obj)
|
||||
else:
|
||||
roles = self.getRoles()
|
||||
return role in roles
|
||||
|
||||
def has_permission(self, permission, obj):
|
||||
return self.request.zopeUser.has_permission(permission, obj)
|
||||
|
||||
def getRoles(self):
|
||||
'''This method collects all the roles for this user, not simply
|
||||
user.roles, but also roles inherited from group membership.'''
|
||||
return self.getZopeUser().getRoles()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
try:
|
||||
from AccessControl.PermissionRole import _what_not_even_god_should_do, \
|
||||
rolesForPermissionOn
|
||||
from Acquisition import aq_base
|
||||
except ImportError:
|
||||
pass # For those using Appy without Zope
|
||||
|
||||
class ZopeUserPatches:
|
||||
'''This class is a fake one that defines Appy variants of some of Zope's
|
||||
AccessControl.User methods. The idea is to implement the notion of group
|
||||
of users.'''
|
||||
|
||||
def getRoles(self):
|
||||
'''Returns the global roles that this user (or any of its groups)
|
||||
possesses.'''
|
||||
res = list(self.roles)
|
||||
if 'Anonymous' not in res: res.append('Authenticated')
|
||||
# Add group global roles
|
||||
if not hasattr(aq_base(self), 'groups'): return res
|
||||
for roles in self.groups.itervalues():
|
||||
for role in roles:
|
||||
if role not in res: res.append(role)
|
||||
return res
|
||||
|
||||
def getRolesInContext(self, object):
|
||||
'''Return the list of global and local (to p_object) roles granted to
|
||||
this user (or to any of its groups).'''
|
||||
if isinstance(object, AbstractWrapper): object = object.o
|
||||
object = getattr(object, 'aq_inner', object)
|
||||
# Start with user global roles
|
||||
res = self.getRoles()
|
||||
# Add local roles
|
||||
localRoles = getattr(object, '__ac_local_roles__', None)
|
||||
if not localRoles: return res
|
||||
userId = self.getId()
|
||||
groups = getattr(self, 'groups', ())
|
||||
for id, roles in localRoles.iteritems():
|
||||
if (id != userId) and (id not in groups): continue
|
||||
for role in roles:
|
||||
if role not in res: res.append(role)
|
||||
return res
|
||||
|
||||
def allowed(self, object, object_roles=None):
|
||||
'''Checks whether the user has access to p_object. The user (or one of
|
||||
its groups) must have one of the roles in p_object_roles.'''
|
||||
if object_roles is _what_not_even_god_should_do: return 0
|
||||
# If "Anonymous" is among p_object_roles, grant access.
|
||||
if (object_roles is None) or ('Anonymous' in object_roles): return 1
|
||||
# If "Authenticated" is among p_object_roles, grant access if the user
|
||||
# is not anonymous.
|
||||
if 'Authenticated' in object_roles and \
|
||||
(self.getUserName() != 'Anonymous User'):
|
||||
if self._check_context(object): return 1
|
||||
# Try first to grant access based on global user roles
|
||||
'''Has the logged user p_permission on p_obj?'''
|
||||
obj = obj.o
|
||||
# What are the roles which are granted p_permission on p_obj?
|
||||
allowedRoles = obj.getRolesFor(permission)
|
||||
# Grant access if "Anonymous" is among roles.
|
||||
if ('Anonymous' in allowedRoles): return True
|
||||
# Grant access if "Authenticated" is among p_roles and the user is not
|
||||
# anonymous.
|
||||
if ('Authenticated' in allowedRoles) and (self.o.id != 'anon'):
|
||||
return True
|
||||
# Grant access based on global user roles.
|
||||
for role in self.getRoles():
|
||||
if role not in object_roles: continue
|
||||
if self._check_context(object): return 1
|
||||
return
|
||||
# Try then to grant access based on local roles
|
||||
innerObject = getattr(object, 'aq_inner', object)
|
||||
localRoles = getattr(innerObject, '__ac_local_roles__', None)
|
||||
if role in allowedRoles: return True
|
||||
# Grant access based on local roles
|
||||
localRoles = getattr(obj.aq_base, '__ac_local_roles__', None)
|
||||
if not localRoles: return
|
||||
userId = self.getId()
|
||||
groups = getattr(self, 'groups', ())
|
||||
for id, roles in localRoles.iteritems():
|
||||
if (id != userId) and (id not in groups): continue
|
||||
# Gets the logins of this user and all its groups.
|
||||
userLogins = self.getLogins()
|
||||
for login, roles in localRoles.iteritems():
|
||||
# Ignore logins not corresponding to this user.
|
||||
if login not in logins: continue
|
||||
for role in roles:
|
||||
if role not in object_roles: continue
|
||||
if self._check_context(object): return 1
|
||||
return
|
||||
|
||||
try:
|
||||
from AccessControl.User import SimpleUser
|
||||
SimpleUser.getRoles = getRoles
|
||||
SimpleUser.getRolesInContext = getRolesInContext
|
||||
SimpleUser.allowed = allowed
|
||||
except ImportError:
|
||||
pass
|
||||
if role in allowedRoles: return True
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -533,7 +533,7 @@ class AbstractWrapper(object):
|
|||
</table>''')
|
||||
|
||||
pxView = Px('''
|
||||
<x var="x=zobj.allows('View', raiseError=True);
|
||||
<x var="x=zobj.allows('read', raiseError=True);
|
||||
errors=req.get('errors', {});
|
||||
layout=zobj.getPageLayout(layoutType);
|
||||
phaseObj=zobj.getAppyPhases(currentOnly=True, layoutType='view');
|
||||
|
@ -549,7 +549,7 @@ class AbstractWrapper(object):
|
|||
</x>''', template=pxTemplate, hook='content')
|
||||
|
||||
pxEdit = Px('''
|
||||
<x var="x=zobj.allows('Modify portal content', raiseError=True);
|
||||
<x var="x=zobj.allows('write', raiseError=True);
|
||||
errors=req.get('errors', None) or {};
|
||||
layout=zobj.getPageLayout(layoutType);
|
||||
cssJs={};
|
||||
|
@ -625,9 +625,9 @@ class AbstractWrapper(object):
|
|||
def _getParentAttr(klass, attr):
|
||||
'''Gets value of p_attr on p_klass base classes (if this attr exists).
|
||||
Scan base classes in the reverse order as Python does. Used by
|
||||
classmethods m_getWorkflow and m_getCreators below. Scanning base
|
||||
classes in reverse order allows user-defined elements to override
|
||||
default Appy elements.'''
|
||||
classmethod m_getWorkflow below. Scanning base classes in reverse
|
||||
order allows user-defined elements to override default Appy
|
||||
elements.'''
|
||||
i = len(klass.__bases__) - 1
|
||||
res = None
|
||||
while i >= 0:
|
||||
|
@ -643,15 +643,6 @@ class AbstractWrapper(object):
|
|||
if not res: res = WorkflowAnonymous
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def getCreators(klass, cfg):
|
||||
'''Returns the roles that are allowed to create instances of p_klass.
|
||||
p_cfg is the product config that holds the default value.'''
|
||||
res = klass._getParentAttr('creators')
|
||||
# Return default creators if no creators was found.
|
||||
if not res: res = cfg.appConfig.defaultCreators
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def getIndexes(klass, includeDefaults=True):
|
||||
'''Returns a dict whose keys are the names of the indexes that are
|
||||
|
@ -823,7 +814,6 @@ class AbstractWrapper(object):
|
|||
if isField:
|
||||
# Link the object to this one
|
||||
appyType.linkObject(self.o, zopeObj)
|
||||
zopeObj._appy_managePermissions()
|
||||
# Call custom initialization
|
||||
if externalData: param = externalData
|
||||
else: param = True
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue