[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:
Gaetan Delannay 2013-08-23 18:57:27 +02:00
parent 04852360fa
commit 5223af2a62
14 changed files with 198 additions and 450 deletions

View file

@ -246,14 +246,14 @@ class Field:
elif rp and isinstance(rp, basestring): elif rp and isinstance(rp, basestring):
self.readPermission = rp self.readPermission = rp
else: else:
self.readPermission = 'View' self.readPermission = 'read'
wp = self.specificWritePermission wp = self.specificWritePermission
if wp and not isinstance(wp, basestring): if wp and not isinstance(wp, basestring):
self.writePermission = '%s: Write %s %s' % (appName, prefix, name) self.writePermission = '%s: Write %s %s' % (appName, prefix, name)
elif wp and isinstance(wp, basestring): elif wp and isinstance(wp, basestring):
self.writePermission = wp self.writePermission = wp
else: else:
self.writePermission = 'Modify portal content' self.writePermission = 'write'
if (self.type == 'Ref') and not self.isBack: if (self.type == 'Ref') and not self.isBack:
# We must initialise the corresponding back reference # We must initialise the corresponding back reference
self.back.klass = klass self.back.klass = klass

View file

@ -140,7 +140,7 @@ class Ref(Field):
folder=zobj.getCreateFolder(); folder=zobj.getCreateFolder();
tiedClassName=ztool.getPortalType(field.klass); tiedClassName=ztool.getPortalType(field.klass);
canWrite=not field.isBack and zobj.allows(field.writePermission); canWrite=not field.isBack and zobj.allows(field.writePermission);
showPlusIcon=zobj.mayAddReference(field.name); showPlusIcon=field.mayAdd(zobj);
atMostOneRef=(field.multiplicity[1] == 1) and \ atMostOneRef=(field.multiplicity[1] == 1) and \
(len(zobjects)<=1); (len(zobjects)<=1);
addConfirmMsg=field.addConfirm and \ addConfirmMsg=field.addConfirm and \
@ -592,12 +592,8 @@ class Ref(Field):
# May the user edit this Ref field? # May the user edit this Ref field?
if not obj.allows(self.writePermission): if not obj.allows(self.writePermission):
return gutils.No('no_write_perm') return gutils.No('no_write_perm')
# Have the user the correct add permission? # May the user create instances of the referred class?
tool = obj.getTool() if not obj.getTool().userMayCreate(self.klass):
addPermission = '%s: Add %s' % (tool.getAppName(),
tool.getPortalType(self.klass))
folder = obj.getCreateFolder()
if not tool.getUser().has_permission(addPermission, folder):
return gutils.No('no_add_perm') return gutils.No('no_add_perm')
return True return True

View file

@ -147,7 +147,7 @@ class UiSearch:
pxView = Px(''' pxView = Px('''
<div class="portletSearch"> <div class="portletSearch">
<a href=":'%s?className=%s&amp;search=%s' % \ <a href=":'%s?className=%s&amp;search=%s' % \
(queryUrl, rootClass, search.name)" (queryUrl, className, search.name)"
class=":search.name == currentSearch and 'portletCurrent' or ''" class=":search.name == currentSearch and 'portletCurrent' or ''"
title=":search.translatedDescr">:search.translated</a> title=":search.translatedDescr">:search.translated</a>
</div>''') </div>''')

View file

@ -59,12 +59,6 @@ class Import:
self.sort = sort self.sort = sort
# Workflow-specific types and default workflows -------------------------------- # Workflow-specific types and default workflows --------------------------------
appyToZopePermissions = {
'read': ('View', 'Access contents information'),
'write': 'Modify portal content',
'delete': 'Delete objects',
}
class Role: class Role:
'''Represents a role.''' '''Represents a role.'''
zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated') zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated')
@ -108,7 +102,7 @@ class State:
name of a role, this method returns self.usedRoles[role] if it name of a role, this method returns self.usedRoles[role] if it
exists, or creates a Role instance, puts it in self.usedRoles and exists, or creates a Role instance, puts it in self.usedRoles and
returns it else. If it is a Role instance, the method stores it in returns it else. If it is a Role instance, the method stores it in
self.usedRoles if it not in it yet and returns it.''' self.usedRoles if it is not in it yet and returns it.'''
if isinstance(role, basestring): if isinstance(role, basestring):
if role in self.usedRoles: if role in self.usedRoles:
return self.usedRoles[role] return self.usedRoles[role]
@ -135,61 +129,6 @@ class State:
def getUsedRoles(self): return self.usedRoles.values() def getUsedRoles(self): return self.usedRoles.values()
def getPermissions(self):
'''If you get the permissions mapping through self.permissions, dict
values may be of different types (a list of roles, a single role or
None). Iy you call this method, you will always get a list which
may be empty.'''
res = {}
for permission, roleValue in self.permissions.iteritems():
if roleValue == None:
res[permission] = []
elif isinstance(roleValue, basestring):
res[permission] = [roleValue]
else:
res[permission] = roleValue
return res
def updatePermission(self, obj, zopePermission, roleNames):
'''Updates, on p_obj, list of p_roleNames which are granted a given
p_zopePermission. This method returns True if the list has been
effectively updated.'''
attr = Permission.getZopeAttrName(zopePermission)
if not hasattr(obj.aq_base, attr) or \
(getattr(obj.aq_base, attr) != roleNames):
setattr(obj, attr, roleNames)
return True
return False
def updatePermissions(self, wf, obj):
'''Zope requires permission-to-roles mappings to be stored as attributes
on the object itself. This method does this job, duplicating the info
from this state definition on p_obj. p_res is True if at least one
change has been effectively performed.'''
res = False
for permission, roles in self.getPermissions().iteritems():
roleNames = tuple([role.name for role in roles])
# Compute Zope permission(s) related to this permission.
if appyToZopePermissions.has_key(permission):
# It is a standard permission (r, w, d)
zopePerm = appyToZopePermissions[permission]
elif isinstance(permission, basestring):
# It is a user-defined permission
zopePerm = permission
else:
# It is a Permission instance
appName = obj.getProductConfig().PROJECTNAME
zopePerm = permission.getName(wf, appName)
# zopePerm contains a single permission or a tuple of permissions
if isinstance(zopePerm, basestring):
changed = self.updatePermission(obj, zopePerm, roleNames)
res = res or changed
else:
for zPerm in zopePerm:
changed = self.updatePermission(obj, zPerm, roleNames)
res = res or changed
return res
class Transition: class Transition:
def __init__(self, states, condition=True, action=None, notify=None, def __init__(self, states, condition=True, action=None, notify=None,
show=True, confirm=False): show=True, confirm=False):
@ -345,8 +284,6 @@ class Transition:
if not doHistory: comment = '_invisible_' if not doHistory: comment = '_invisible_'
obj.addHistoryEvent(action, review_state=targetStateName, obj.addHistoryEvent(action, review_state=targetStateName,
comments=comment) comments=comment)
# Update permissions-to-roles attributes
targetState.updatePermissions(wf, obj)
# Reindex the object if required. Not only security-related indexes # Reindex the object if required. Not only security-related indexes
# (Allowed, State) need to be updated here. # (Allowed, State) need to be updated here.
if not obj.isTemporary(): obj.reindex() if not obj.isTemporary(): obj.reindex()
@ -384,8 +321,7 @@ class Permission:
self.fieldDescriptor = fieldDescriptor self.fieldDescriptor = fieldDescriptor
def getName(self, wf, appName): def getName(self, wf, appName):
'''Returns the name of the Zope permission that corresponds to this '''Returns the name of this permission.'''
permission.'''
className, fieldName = self.fieldDescriptor.rsplit('.', 1) className, fieldName = self.fieldDescriptor.rsplit('.', 1)
if className.find('.') == -1: if className.find('.') == -1:
# The related class resides in the same module as the workflow # The related class resides in the same module as the workflow
@ -398,16 +334,6 @@ class Permission:
else: access = 'Write' else: access = 'Write'
return '%s: %s %s %s' % (appName, access, fullClassName, fieldName) return '%s: %s %s %s' % (appName, access, fullClassName, fieldName)
@staticmethod
def getZopeAttrName(zopePermission):
'''Gets the name of the attribute where Zope stores, on every object,
the tuple of roles who are granted a given p_zopePermission.'''
res = ''
for c in zopePermission:
if c in Permission.allowedChars: res += c
else: res += '_'
return '_%s_Permission' % res
class ReadPermission(Permission): pass class ReadPermission(Permission): pass
class WritePermission(Permission): pass class WritePermission(Permission): pass
@ -475,7 +401,7 @@ class Config:
languageSelector = False languageSelector = False
# People having one of these roles will be able to create instances # People having one of these roles will be able to create instances
# of classes defined in your application. # of classes defined in your application.
defaultCreators = ['Manager', 'Owner'] defaultCreators = ['Manager']
# Number of translations for every page on a Translation object # Number of translations for every page on a Translation object
translationsPerPage = 30 translationsPerPage = 30
# Language that will be used as a basis for translating to other # Language that will be used as a basis for translating to other

View file

@ -557,12 +557,6 @@ class ZopeGenerator(Generator):
if theImport not in imports: if theImport not in imports:
imports.append(theImport) imports.append(theImport)
repls['imports'] = '\n'.join(imports) repls['imports'] = '\n'.join(imports)
# Compute list of add permissions
addPermissions = ''
for classDescr in classesAll:
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
self.applicationName, classDescr.name)
repls['addPermissions'] = addPermissions
# Compute root classes # Compute root classes
repls['rootClasses'] = ','.join(["'%s'" % c.name \ repls['rootClasses'] = ','.join(["'%s'" % c.name \
for c in classesButTool if c.isRoot()]) for c in classesButTool if c.isRoot()])

View file

@ -63,7 +63,6 @@ class ZopeInstaller:
self.productName = config.PROJECTNAME self.productName = config.PROJECTNAME
self.languages = config.appConfig.languages self.languages = config.appConfig.languages
self.logger = config.logger self.logger = config.logger
self.addContentPermissions = config.ADD_CONTENT_PERMISSIONS
def installUi(self): def installUi(self):
'''Installs the user interface.''' '''Installs the user interface.'''
@ -157,14 +156,9 @@ class ZopeInstaller:
catalog.reindexIndex('SearchableText', self.app.REQUEST) catalog.reindexIndex('SearchableText', self.app.REQUEST)
self.logger.info('Done.') self.logger.info('Done.')
def getAddPermission(self, className):
'''What is the name of the permission allowing to create instances of
class whose name is p_className?'''
return self.productName + ': Add ' + className
def installBaseObjects(self): def installBaseObjects(self):
'''Creates the tool and the root data folder if they do not exist.''' '''Creates the tool and the base data folder if they do not exist.'''
# Create or update the base folder for storing data # Create the tool.
zopeContent = self.app.objectIds() zopeContent = self.app.objectIds()
from OFS.Folder import manage_addFolder from OFS.Folder import manage_addFolder
@ -172,29 +166,8 @@ class ZopeInstaller:
toolName = '%sTool' % self.productName toolName = '%sTool' % self.productName
gutils.createObject(self.app, 'config', toolName, self.productName, gutils.createObject(self.app, 'config', toolName, self.productName,
wf=False, noSecurity=True) wf=False, noSecurity=True)
# Create the base data folder.
if 'data' not in zopeContent: if 'data' not in zopeContent: manage_addFolder(self.app, 'data')
manage_addFolder(self.app, 'data')
data = self.app.data
tool = self.app.config
# Manager has been granted Add permissions for all root classes.
# This may not be desired, so remove this.
for className in self.config.rootClasses:
permission = self.getAddPermission(className)
data.manage_permission(permission, (), acquire=0)
# All roles defined as creators should be able to create the
# corresponding root classes in this folder.
i = -1
for klass in self.config.appClasses:
i += 1
if not klass.__dict__.has_key('root') or \
not klass.__dict__['root']:
continue # It is not a root class
className = self.config.appClassNames[i]
wrapperClass = tool.getAppyClass(className, wrapper=True)
creators = wrapperClass.getCreators(self.config)
permission = self.getAddPermission(className)
gutils.updateRolesForPermission(permission,tuple(creators),data)
# Remove some default objects created by Zope but not useful to Appy # Remove some default objects created by Zope but not useful to Appy
for name in ('standard_html_footer', 'standard_html_header',\ for name in ('standard_html_footer', 'standard_html_header',\
@ -206,14 +179,13 @@ class ZopeInstaller:
inner objects (users, groups, translations, documents).''' inner objects (users, groups, translations, documents).'''
tool = self.app.config tool = self.app.config
tool.createOrUpdate(True, None) tool.createOrUpdate(True, None)
tool.refreshSecurity()
appyTool = tool.appy() appyTool = tool.appy()
appyTool.log('Appy version is "%s".' % appy.version.short) appyTool.log('Appy version is "%s".' % appy.version.short)
# Create the default users if they do not exist. # Create the default users if they do not exist.
for login, roles in self.defaultUsers.iteritems(): for login, roles in self.defaultUsers.iteritems():
if not appyTool.count('User', noSecurity=True, login=login): if not appyTool.count('User', noSecurity=True, login=login):
appyTool.create('users', noSecurity=True, login=login, appyTool.create('users', noSecurity=True, id=login, login=login,
password1=login, password2=login, password1=login, password2=login,
email='%s@appyframework.org'%login, roles=roles) email='%s@appyframework.org'%login, roles=roles)
appyTool.log('User "%s" created.' % login) appyTool.log('User "%s" created.' % login)
@ -332,8 +304,7 @@ class ZopeInstaller:
wrapper = klass.wrapperClass wrapper = klass.wrapperClass
exec 'from %s import manage_add%s as ctor' % (module, name) exec 'from %s import manage_add%s as ctor' % (module, name)
self.zopeContext.registerClass(meta_type=name, self.zopeContext.registerClass(meta_type=name,
constructors = (ctor,), constructors = (ctor,), permission = None)
permission = self.addContentPermissions[name])
# Create workflow prototypical instances in __instance__ attributes # Create workflow prototypical instances in __instance__ attributes
wf = wrapper.getWorkflow() wf = wrapper.getWorkflow()
if not hasattr(wf, '__instance__'): wf.__instance__ = wf() if not hasattr(wf, '__instance__'): wf.__instance__ = wf()

View file

@ -199,7 +199,8 @@ class ToolMixin(BaseMixin):
def getRootClasses(self): def getRootClasses(self):
'''Returns the list of root classes for this application.''' '''Returns the list of root classes for this application.'''
return self.getProductConfig().rootClasses cfg = self.getProductConfig()
return [self.getAppyClass(k) for k in cfg.rootClasses]
def _appy_getAllFields(self, className): def _appy_getAllFields(self, className):
'''Returns the (translated) names of fields of p_className.''' '''Returns the (translated) names of fields of p_className.'''
@ -265,8 +266,8 @@ class ToolMixin(BaseMixin):
p_className.''' p_className.'''
appyClass = self.getAppyClass(className) appyClass = self.getAppyClass(className)
importParams = self.getCreateMeans(appyClass)['import'] importParams = self.getCreateMeans(appyClass)['import']
onElement = importParams['onElement'].__get__('') onElement = importParams.onElement.__get__('')
sortMethod = importParams['sort'] sortMethod = importParams.sort
if sortMethod: sortMethod = sortMethod.__get__('') if sortMethod: sortMethod = sortMethod.__get__('')
elems = [] elems = []
importType = self.getAppyType('importPathFor%s' % className) importType = self.getAppyType('importPathFor%s' % className)
@ -280,7 +281,7 @@ class ToolMixin(BaseMixin):
elems.append(elemInfo) elems.append(elemInfo)
if sortMethod: if sortMethod:
elems = sortMethod(elems) elems = sortMethod(elems)
return [importParams['headers'], elems] return [importParams.headers, elems]
def showPortlet(self, context, layoutType): def showPortlet(self, context, layoutType):
'''When must the portlet be shown?''' '''When must the portlet be shown?'''
@ -489,17 +490,13 @@ class ToolMixin(BaseMixin):
if wrapper: return zopeClass.wrapperClass if wrapper: return zopeClass.wrapperClass
else: return zopeClass.wrapperClass.__bases__[-1] else: return zopeClass.wrapperClass.__bases__[-1]
def getCreateMeans(self, contentTypeOrAppyClass): def getCreateMeans(self, klass):
'''Gets the different ways objects of p_contentTypeOrAppyClass (which '''Gets the different ways objects of p_klass can be created (via a web
can be a Zope content type or a Appy class) can be created form, by importing external data, etc). Result is a dict whose keys
(via a web form, by importing external data, etc). Result is a are strings (ie "form", "import"...) and whose values are additional
dict whose keys are strings (ie "form", "import"...) and whose data about the particular mean.'''
values are additional data about the particular mean.'''
pythonClass = contentTypeOrAppyClass
if isinstance(contentTypeOrAppyClass, basestring):
pythonClass = self.getAppyClass(pythonClass)
res = {} res = {}
if not pythonClass.__dict__.has_key('create'): if not klass.__dict__.has_key('create'):
res['form'] = None res['form'] = None
# No additional data for this means, which is the default one. # No additional data for this means, which is the default one.
else: else:
@ -511,21 +508,26 @@ class ToolMixin(BaseMixin):
if isinstance(mean, basestring): if isinstance(mean, basestring):
res[mean] = None res[mean] = None
else: else:
res[mean.id] = mean.__dict__ res[mean.id] = mean
else: else:
res[means.id] = means.__dict__ res[means.id] = means
return res return res
def userMaySearch(self, rootClass): def userMaySearch(self, rootClass):
'''This method checks if the currently logged user can trigger searches '''May the logged user search among instances of p_rootClass ?'''
on a given p_rootClass. This is done by calling method "maySearch" # When editing a form, one should avoid annoying the user with this.
on the class. If no such method exists, we return True.'''
# When editign a form, one should avoid annoying the user with this.
url = self.REQUEST['ACTUAL_URL'] url = self.REQUEST['ACTUAL_URL']
if url.endswith('/edit') or url.endswith('/do'): return if url.endswith('/edit') or url.endswith('/do'): return
pythonClass = self.getAppyClass(rootClass) if 'maySearch' in rootClass.__dict__:
if 'maySearch' in pythonClass.__dict__: return pythonClass.rootClass(self.appy())
return pythonClass.maySearch(self.appy()) return True
def userMayCreate(self, klass):
'''May the logged user create instances of p_klass ?'''
allowedRoles = getattr(klass, 'creators', None) or \
self.getProductConfig().appConfig.defaultCreators
for role in self.getUser().getRoles():
if role in allowedRoles:
return True return True
def onImportObjects(self): def onImportObjects(self):
@ -737,23 +739,22 @@ class ToolMixin(BaseMixin):
obj = self.getObject(objectUid) obj = self.getObject(objectUid)
return obj, fieldName return obj, fieldName
def getGroupedSearches(self, className): def getGroupedSearches(self, klass):
'''Returns an object with 2 attributes: '''Returns an object with 2 attributes:
* "searches" stores the searches that are defined for p_className; * "searches" stores the searches that are defined for p_klass;
* "default" stores the search defined as the default one. * "default" stores the search defined as the default one.
Every item representing a search is a dict containing info about a Every item representing a search is a dict containing info about a
search or about a group of searches. search or about a group of searches.
''' '''
appyClass = self.getAppyClass(className)
res = [] res = []
default = None # Also retrieve the default one here. default = None # Also retrieve the default one here.
groups = {} # The already encountered groups groups = {} # The already encountered groups
page = Page('main') # A dummy page required by class UiGroup page = Page('main') # A dummy page required by class UiGroup
# Get the searches statically defined on the class # Get the searches statically defined on the class
searches = ClassDescriptor.getSearches(appyClass, tool=self.appy()) searches = ClassDescriptor.getSearches(klass, tool=self.appy())
# Get the dynamically computed searches # Get the dynamically computed searches
if hasattr(appyClass, 'getDynamicSearches'): if hasattr(klass, 'getDynamicSearches'):
searches += appyClass.getDynamicSearches(self.appy()) searches += klass.getDynamicSearches(self.appy())
for search in searches: for search in searches:
# Create the search descriptor # Create the search descriptor
uiSearch = UiSearch(search, className, self) uiSearch = UiSearch(search, className, self)
@ -792,9 +793,8 @@ class ToolMixin(BaseMixin):
if ui: res = UiSearch(res, className, self) if ui: res = UiSearch(res, className, self)
return res return res
def advancedSearchEnabledFor(self, className): def advancedSearchEnabledFor(self, klass):
'''Is advanced search visible for p_klass ?''' '''Is advanced search visible for p_klass ?'''
klass = self.getAppyClass(className)
# By default, advanced search is enabled. # By default, advanced search is enabled.
if not hasattr(klass, 'searchAdvanced'): return True if not hasattr(klass, 'searchAdvanced'): return True
# Evaluate attribute "show" on this Search instance representing the # Evaluate attribute "show" on this Search instance representing the
@ -1103,29 +1103,26 @@ class ToolMixin(BaseMixin):
login = (rq.__class__.__name__ == 'Object') and 'system' or 'anon' login = (rq.__class__.__name__ == 'Object') and 'system' or 'anon'
# Get the User object from a query in the catalog. # Get the User object from a query in the catalog.
user = tool.search1('User', noSecurity=True, login=login) user = tool.search1('User', noSecurity=True, login=login)
# It is possible that we find no user here: it happens before users
# "anon" and "system" are created, at first Zope startup.
if not user: return
rq.user = user rq.user = user
# Precompute some values or this usser for performance reasons # Precompute some values or this user for performance reasons.
rq.userRoles = user.getRoles() rq.userRoles = user.getRoles()
rq.userLogins = user.getLogins()
rq.zopeUser = user.getZopeUser() rq.zopeUser = user.getZopeUser()
return user return user
#from AccessControl import getSecurityManager
#user = getSecurityManager().getUser()
#if not user:
# from AccessControl.User import nobody
# return nobody
#return user
def getUserLine(self): def getUserLine(self):
'''Returns a info about the currently logged user as a 2-tuple: first '''Returns a info about the currently logged user as a 2-tuple: first
elem is the one-line user info as shown on every page; second line is elem is the one-line user info as shown on every page; second line is
the URL to edit user info.''' the URL to edit user info.'''
user = self.getUser() user = self.getUser()
userRoles = self.appy().request.userRoles
info = [user.title] info = [user.title]
rolesToShow = [r for r in userRoles if r != 'Authenticated'] showable = [r for r in user.getRoles() if r != 'Authenticated']
if rolesToShow: if showable:
info.append(', '.join([self.translate('role_%s' % r) \ info.append(', '.join([self.translate('role_%s' % r) \
for r in rolesToShow])) for r in showable]))
# Edit URL for the user. # Edit URL for the user.
url = None url = None
if user.o.mayEdit(): if user.o.mayEdit():
@ -1134,17 +1131,17 @@ class ToolMixin(BaseMixin):
def getUserName(self, login=None, normalized=False): def getUserName(self, login=None, normalized=False):
'''Gets the user name corresponding to p_login (or the currently logged '''Gets the user name corresponding to p_login (or the currently logged
login if None), or the p_login itself if the user does not exist user if None), or the p_login itself if the user does not exist
anymore. If p_normalized is True, special chars in the first and last anymore. If p_normalized is True, special chars in the first and last
names are normalized.''' names are normalized.'''
tool = self.appy() tool = self.appy()
if not login: login = tool.user.login if not login: login = tool.user.login
# Manage the special case of an anonymous user. # Manage the special case of an anonymous user.
if login == 'Anonymous User': if login == 'anon':
name = self.translate('anonymous') name = self.translate('anonymous')
if normalized: name = sutils.normalizeString(name) if normalized: name = sutils.normalizeString(name)
return name return name
# Manage the case of a "real" user. # Manage the case of any other user.
user = tool.search1('User', noSecurity=True, login=login) user = tool.search1('User', noSecurity=True, login=login)
if not user: return login if not user: return login
firstName = user.firstName firstName = user.firstName

View file

@ -93,9 +93,6 @@ class BaseMixin:
# Manage potential link with an initiator object # Manage potential link with an initiator object
if created and initiator: initiator.appy().link(initiatorField.name,obj) if created and initiator: initiator.appy().link(initiatorField.name,obj)
# Manage "add" permissions and reindex the object
obj._appy_managePermissions()
# Call the custom "onEdit" if available # Call the custom "onEdit" if available
msg = None # The message to display to the user. It can be set by onEdit msg = None # The message to display to the user. It can be set by onEdit
if obj.wrapperClass: if obj.wrapperClass:
@ -410,7 +407,7 @@ class BaseMixin:
return self.goto(tool.getSiteUrl(), msg) return self.goto(tool.getSiteUrl(), msg)
# If the user can't access the object anymore, redirect him to the # If the user can't access the object anymore, redirect him to the
# main site page. # main site page.
if not obj.allows('View'): if not obj.allows('read'):
return self.goto(tool.getSiteUrl(), msg) return self.goto(tool.getSiteUrl(), msg)
if (buttonClicked == 'save') or saveConfirmed: if (buttonClicked == 'save') or saveConfirmed:
obj.say(msg) obj.say(msg)
@ -484,7 +481,7 @@ class BaseMixin:
corresponding Appy wrapper and returns, as XML, the its result.''' corresponding Appy wrapper and returns, as XML, the its result.'''
self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8') self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8')
# Check if the user is allowed to consult this object # Check if the user is allowed to consult this object
if not self.allows('View'): if not self.allows('read'):
return XmlMarshaller().marshall('Unauthorized') return XmlMarshaller().marshall('Unauthorized')
if not action: if not action:
marshaller = XmlMarshaller(rootTag=self.getClass().__name__, marshaller = XmlMarshaller(rootTag=self.getClass().__name__,
@ -670,11 +667,6 @@ class BaseMixin:
if not allValues: return '' if not allValues: return ''
return getattr(allValues[rowIndex], name, '') return getattr(allValues[rowIndex], name, '')
def mayAddReference(self, name):
'''May the user add references via Ref field named p_name in
p_folder?'''
return self.getAppyType(name).mayAdd(self)
def isDebug(self): def isDebug(self):
'''Are we in debug mode ?''' '''Are we in debug mode ?'''
for arg in sys.argv: for arg in sys.argv:
@ -937,30 +929,6 @@ class BaseMixin:
stateName = stateName or self.State() stateName = stateName or self.State()
return '%s_%s' % (self.getWorkflow(name=True), stateName) return '%s_%s' % (self.getWorkflow(name=True), stateName)
def refreshSecurity(self):
'''Refresh security info on this object. Returns True if the info has
effectively been updated.'''
wf = self.getWorkflow()
try:
# Get the state definition of the object's current state.
state = getattr(wf, self.State())
except AttributeError:
# The workflow information for this object does not correspond to
# its current workflow attribution. Add a new fake event
# representing passage of this object to the initial state of his
# currently attributed workflow.
stateName = self.State(name=True, initial=True)
self.addHistoryEvent(None, review_state=stateName)
state = self.State(name=False, initial=True)
self.log('Wrong workflow info for a "%s"; is now in state "%s".' % \
(self.meta_type, stateName))
# Update permission attributes on the object if required
updated = state.updatePermissions(wf, self)
if updated:
# Reindex the object because security-related info is indexed.
self.reindex()
return updated
def applyUserIdChange(self, oldId, newId): def applyUserIdChange(self, oldId, newId):
'''A user whose ID was p_oldId has now p_newId. If the old ID was '''A user whose ID was p_oldId has now p_newId. If the old ID was
mentioned in self's local roles, update it to the new ID. This mentioned in self's local roles, update it to the new ID. This
@ -1105,14 +1073,14 @@ class BaseMixin:
def mayDelete(self): def mayDelete(self):
'''May the currently logged user delete this object?''' '''May the currently logged user delete this object?'''
res = self.allows('Delete objects') res = self.allows('delete')
if not res: return if not res: return
# An additional, user-defined condition, may refine the base permission. # An additional, user-defined condition, may refine the base permission.
appyObj = self.appy() appyObj = self.appy()
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete() if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
return True return True
def mayEdit(self, permission='Modify portal content'): def mayEdit(self, permission='write'):
'''May the currently logged user edit this object? p_perm can be a '''May the currently logged user edit this object? p_perm can be a
field-specific permission.''' field-specific permission.'''
res = self.allows(permission) res = self.allows(permission)
@ -1195,6 +1163,12 @@ class BaseMixin:
self.reindex() self.reindex()
return self.goto(self.getUrl(rq['HTTP_REFERER'])) return self.goto(self.getUrl(rq['HTTP_REFERER']))
def getRolesFor(self, permission):
'''Gets, according to the workflow, the roles that are currently granted
p_permission on this object.'''
state = self.State(name=False)
return [role.name for role in state.permissions[permission]]
def appy(self): def appy(self):
'''Returns a wrapper object allowing to manipulate p_self the Appy '''Returns a wrapper object allowing to manipulate p_self the Appy
way.''' way.'''
@ -1284,18 +1258,17 @@ class BaseMixin:
'''Returns the list of roles and users that are allowed to view this '''Returns the list of roles and users that are allowed to view this
object. This index value will be used within catalog queries for object. This index value will be used within catalog queries for
filtering objects the user is allowed to see.''' filtering objects the user is allowed to see.'''
res = set() # Get, from the workflow, roles having permission 'read'.
# Get, from the workflow, roles having permission 'View'. res = self.getRolesFor('read')
for role in self.getProductConfig().rolesForPermissionOn('View', self): # Add users or groups having, locally, this role on this object.
res.add(role) localRoles = getattr(self.aq_base, '__ac_local_roles__', None)
# Add users having, locally, this role on this object. if not localRoles: return res
localRoles = getattr(self, '__ac_local_roles__', None)
if not localRoles: return list(res)
for id, roles in localRoles.iteritems(): for id, roles in localRoles.iteritems():
for role in roles: for role in roles:
if role in res: if role in res:
res.add('user:%s' % id) usr = 'user:%s' % id
return list(res) if usr not in res: res.append(usr)
return res
def showState(self): def showState(self):
'''Must I show self's current state ?''' '''Must I show self's current state ?'''
@ -1326,39 +1299,6 @@ class BaseMixin:
res.append((elem, self.translate(self.getWorkflowLabel(elem)))) res.append((elem, self.translate(self.getWorkflowLabel(elem))))
return res return res
def _appy_managePermissions(self):
'''When an object is created or updated, we must update "add"
permissions accordingly: if the object is a folder, we must set on
it permissions that will allow to create, inside it, objects through
Ref fields; if it is not a folder, we must update permissions on its
parent folder instead.'''
# Determine on which folder we need to set "add" permissions
folder = self.getCreateFolder()
# On this folder, set "add" permissions for every content type that will
# be created through reference fields
allCreators = {} # One key for every add permission
addPermissions = self.getProductConfig().ADD_CONTENT_PERMISSIONS
for appyType in self.getAllAppyTypes():
if appyType.type != 'Ref': continue
if appyType.isBack or appyType.link: continue
# Indeed, no possibility to create objects with such Refs
tool = self.getTool()
refType = tool.getPortalType(appyType.klass)
if refType not in addPermissions: continue
# Get roles that may add this content type
appyWrapper = tool.getAppyClass(refType, wrapper=True)
creators = appyWrapper.getCreators(self.getProductConfig())
# Add those creators to the list of creators for this meta_type
addPermission = addPermissions[refType]
if addPermission in allCreators:
allCreators[addPermission] = allCreators[\
addPermission].union(creators)
else:
allCreators[addPermission] = set(creators)
# Update the permissions
for permission, creators in allCreators.iteritems():
updateRolesForPermission(permission, tuple(creators), folder)
getUrlDefaults = {'page':True, 'nav':True} getUrlDefaults = {'page':True, 'nav':True}
def getUrl(self, base=None, mode='view', **kwargs): def getUrl(self, base=None, mode='view', **kwargs):
'''Returns an URL for this object. '''Returns an URL for this object.

View file

@ -13,7 +13,6 @@ from ZPublisher.HTTPRequest import BaseRequest
from OFS.Image import File from OFS.Image import File
from ZPublisher.HTTPRequest import FileUpload from ZPublisher.HTTPRequest import FileUpload
from AccessControl import getSecurityManager from AccessControl import getSecurityManager
from AccessControl.PermissionRole import rolesForPermissionOn
from DateTime import DateTime from DateTime import DateTime
from Products.ExternalMethod.ExternalMethod import ExternalMethod from Products.ExternalMethod.ExternalMethod import ExternalMethod
from Products.Transience.Transience import TransientObjectContainer from Products.Transience.Transience import TransientObjectContainer
@ -24,8 +23,6 @@ logger = logging.getLogger('<!applicationName!>')
# Some global variables -------------------------------------------------------- # Some global variables --------------------------------------------------------
PROJECTNAME = '<!applicationName!>' PROJECTNAME = '<!applicationName!>'
diskFolder = os.path.dirname(<!applicationName!>.__file__) diskFolder = os.path.dirname(<!applicationName!>.__file__)
ADD_CONTENT_PERMISSIONS = {
<!addPermissions!>}
# Applications classes, in various formats # Applications classes, in various formats
rootClasses = [<!rootClasses!>] rootClasses = [<!rootClasses!>]
@ -34,7 +31,7 @@ appClassNames = [<!appClassNames!>]
allClassNames = [<!allClassNames!>] allClassNames = [<!allClassNames!>]
# In the following dict, we store, for every Appy class, the ordered list of # In the following dict, we store, for every Appy class, the ordered list of
# appy types (included inherited ones). # fields.
attributes = {<!attributes!>} attributes = {<!attributes!>}
# Application roles # Application roles

View file

@ -17,17 +17,11 @@ def createObject(folder, id, className, appName, wf=True, noSecurity=False):
user = tool.getUser() user = tool.getUser()
if not noSecurity: if not noSecurity:
# Check that the user can create objects of className. # Check that the user can create objects of className.
userRoles = user.getRoles() klass = ZopeClass.wrapperClass.__bases__[-1]
allowedRoles=ZopeClass.wrapperClass.getCreators(tool.getProductConfig()) if not tool.userMayCreate(klass):
allowed = False
for role in userRoles:
if role in allowedRoles:
allowed = True
break
if not allowed:
from AccessControl import Unauthorized from AccessControl import Unauthorized
raise Unauthorized("User can't create instances of %s" % \ raise Unauthorized("User can't create instances of %s" % \
ZopeClass.__name__) klass.__name__)
obj = ZopeClass(id) obj = ZopeClass(id)
folder._objects = folder._objects + ({'id':id, 'meta_type':className},) folder._objects = folder._objects + ({'id':id, 'meta_type':className},)
folder._setOb(id, obj) folder._setOb(id, obj)
@ -137,21 +131,6 @@ def getClassName(klass, appName=None):
res = klass.__module__.replace('.', '_') + '_' + klass.__name__ res = klass.__module__.replace('.', '_') + '_' + klass.__name__
return res return res
# ------------------------------------------------------------------------------
def updateRolesForPermission(permission, roles, obj):
'''Adds roles from list p_roles to the list of roles that are granted
p_permission on p_obj.'''
from AccessControl.Permission import Permission
# Find existing roles that were granted p_permission on p_obj
existingRoles = ()
for p in obj.ac_inherited_permissions(1):
name, value = p[:2]
if name == permission:
perm = Permission(name, value, obj)
existingRoles = perm.getRoles()
allRoles = set(existingRoles).union(roles)
obj.manage_permission(permission, tuple(allRoles), acquire=0)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def callMethod(obj, method, klass=None, cache=True): def callMethod(obj, method, klass=None, cache=True):
'''This function is used to call a p_method on some Appy p_obj. m_method '''This function is used to call a p_method on some Appy p_obj. m_method

View file

@ -30,55 +30,18 @@ class GroupWrapper(AbstractWrapper):
'''Inter-field validation.''' '''Inter-field validation.'''
return self._callCustom('validate', new, errors) 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): def addUser(self, user):
'''Adds a p_user to this group.''' '''Adds a p_user to this group.'''
# Update the Ref field. # Update the Ref field.
self.link('users', user) 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): def removeUser(self, user):
'''Removes a p_user from this group.''' '''Removes a p_user from this group.'''
self.unlink('users', user) 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): def onEdit(self, created):
# Create or update, on every Zope user of this group, group-related # If the group was created by anon, anon can't stay its Owner.
# information. if 'anon' in self.o.__ac_local_roles__:
# 1. Remove reference to this group for users that were removed from it del self.o.__ac_local_roles__['anon']
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]
return self._callCustom('onEdit', created) return self._callCustom('onEdit', created)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -174,8 +174,8 @@ class ToolWrapper(AbstractWrapper):
</table> </table>
<!-- One section for every searchable root class --> <!-- One section for every searchable root class -->
<x for="rootClass in [rc for rc in rootClasses \ <x for="rootClass in rootClasses" if="ztool.userMaySearch(rootClass)"
if ztool.userMaySearch(rc)]"> var2="className=ztool.getPortalType(rootClass)">
<!-- A separator if required --> <!-- A separator if required -->
<div class="portletSep" var="nb=loop.rootClass.nb" <div class="portletSep" var="nb=loop.rootClass.nb"
@ -188,31 +188,30 @@ class ToolWrapper(AbstractWrapper):
<a var="queryParam=searchInfo.default and \ <a var="queryParam=searchInfo.default and \
searchInfo.default.name or ''" searchInfo.default.name or ''"
href=":'%s?className=%s&amp;search=%s' % \ href=":'%s?className=%s&amp;search=%s' % \
(queryUrl,rootClass,queryParam)" (queryUrl, className, queryParam)"
class=":(not currentSearch and (currentClass==rootClass) and \ class=":(not currentSearch and (currentClass==className) and \
(currentPage=='query')) and \ (currentPage=='query')) and \
'portletCurrent' or ''">::_(rootClass + '_plural')</a> 'portletCurrent' or ''">::_(className + '_plural')</a>
</div> </div>
<!-- Actions --> <!-- Actions -->
<x var="addPermission='%s: Add %s' % (appName, rootClass); <x var="mayCreate=ztool.userMayCreate(rootClass);
userMayAdd=user.has_permission(addPermission, appFolder);
createMeans=ztool.getCreateMeans(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" <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')" style=":url('buttonAdd', bg=True)" value=":_('query_create')"
onclick=":'goto(%s)' % \ onclick=":'goto(%s)' % \
q('%s/do?action=Create&amp;className=%s' % \ q('%s/do?action=Create&amp;className=%s' % \
(toolUrl, rootClass))"/> (toolUrl, className))"/>
<!-- Create object(s) by importing data --> <!-- Create object(s) by importing data -->
<input type="button" class="button" <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')" style=":url('buttonImport', bg=True)" value=":_('query_import')"
onclick=":'goto(%s)' % \ onclick=":'goto(%s)' % \
q('%s/import?className=%s' % (toolUrl, rootClass))"/> q('%s/import?className=%s' % (toolUrl, className))"/>
</x> </x>
<!-- Searches --> <!-- Searches -->
@ -221,7 +220,7 @@ class ToolWrapper(AbstractWrapper):
<!-- Live search --> <!-- Live search -->
<form action=":'%s/do' % toolUrl"> <form action=":'%s/do' % toolUrl">
<input type="hidden" name="action" value="SearchObjects"/> <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"> <table cellpadding="0" cellspacing="0">
<tr valign="bottom"> <tr valign="bottom">
<td><input type="text" size="14" name="w_SearchableText" <td><input type="text" size="14" name="w_SearchableText"
@ -234,13 +233,13 @@ class ToolWrapper(AbstractWrapper):
</form> </form>
<!-- Advanced search --> <!-- Advanced search -->
<div var="highlighted=(currentClass == rootClass) and \ <div var="highlighted=(currentClass == className) and \
(currentPage == 'search')" (currentPage == 'search')"
class=":highlighted and 'portletSearch portletCurrent' or \ class=":highlighted and 'portletSearch portletCurrent' or \
'portletSearch'" 'portletSearch'"
align=":dright"> align=":dright">
<a var="text=_('search_title')" style="font-size: 88%" <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> title=":text"><x>:text</x>...</a>
</div> </div>
</x> </x>
@ -754,16 +753,6 @@ class ToolWrapper(AbstractWrapper):
'''Sends a mail. See doc for appy.gen.mail.sendMail.''' '''Sends a mail. See doc for appy.gen.mail.sendMail.'''
sendMail(self, to, subject, body, attachments=attachments) 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): def refreshCatalog(self, startObject=None):
'''Reindex all Appy objects. For some unknown reason, method '''Reindex all Appy objects. For some unknown reason, method
catalog.refreshCatalog is not able to recatalog Appy objects.''' catalog.refreshCatalog is not able to recatalog Appy objects.'''

View file

@ -140,8 +140,18 @@ class UserWrapper(AbstractWrapper):
else: else:
self.title = self.login 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): def onEdit(self, created):
self.updateTitle() self.updateTitle()
self.ensureAdminIsManager()
aclUsers = self.o.acl_users aclUsers = self.o.acl_users
login = self.login login = self.login
if created: if created:
@ -173,19 +183,25 @@ class UserWrapper(AbstractWrapper):
self.o.manage_addLocalRoles(login, ('Owner',)) self.o.manage_addLocalRoles(login, ('Owner',))
# If the user was created by an Anonymous, Anonymous can't stay Owner # If the user was created by an Anonymous, Anonymous can't stay Owner
# of the object. # of the object.
if None in self.o.__ac_local_roles__: if 'anon' in self.o.__ac_local_roles__:
del self.o.__ac_local_roles__[None] del self.o.__ac_local_roles__['anon']
return self._callCustom('onEdit', created) return self._callCustom('onEdit', created)
def mayEdit(self): 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') custom = self._getCustomMethod('mayEdit')
if custom: return self._callCustom('mayEdit') if custom: return self._callCustom('mayEdit')
else: return True return True
def mayDelete(self): 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') custom = self._getCustomMethod('mayDelete')
if custom: return self._callCustom('mayDelete') if custom: return self._callCustom('mayDelete')
else: return True return True
def getZopeUser(self): def getZopeUser(self):
'''Gets the Zope user corresponding to this user.''' '''Gets the Zope user corresponding to this user.'''
@ -198,97 +214,87 @@ class UserWrapper(AbstractWrapper):
# Call a custom "onDelete" if any. # Call a custom "onDelete" if any.
return self._callCustom('onDelete') 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): def has_role(self, role, obj=None):
zopeUser = self.request.zopeUser '''Has the logged user some p_role? If p_obj is None, check if the user
if obj: return zopeUser.has_role(role, obj) has p_role globally; else, check if he has this p_role in the context
return zopeUser.has_role(role) of p_obj.'''
if obj:
roles = self.getRolesFor(obj)
else:
roles = self.getRoles()
return role in roles
def has_permission(self, permission, obj): def has_permission(self, permission, obj):
return self.request.zopeUser.has_permission(permission, obj) '''Has the logged user p_permission on p_obj?'''
obj = obj.o
def getRoles(self): # What are the roles which are granted p_permission on p_obj?
'''This method collects all the roles for this user, not simply allowedRoles = obj.getRolesFor(permission)
user.roles, but also roles inherited from group membership.''' # Grant access if "Anonymous" is among roles.
return self.getZopeUser().getRoles() if ('Anonymous' in allowedRoles): return True
# Grant access if "Authenticated" is among p_roles and the user is not
# ------------------------------------------------------------------------------ # anonymous.
try: if ('Authenticated' in allowedRoles) and (self.o.id != 'anon'):
from AccessControl.PermissionRole import _what_not_even_god_should_do, \ return True
rolesForPermissionOn # Grant access based on global user roles.
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
for role in self.getRoles(): for role in self.getRoles():
if role not in object_roles: continue if role in allowedRoles: return True
if self._check_context(object): return 1 # Grant access based on local roles
return localRoles = getattr(obj.aq_base, '__ac_local_roles__', None)
# Try then to grant access based on local roles
innerObject = getattr(object, 'aq_inner', object)
localRoles = getattr(innerObject, '__ac_local_roles__', None)
if not localRoles: return if not localRoles: return
userId = self.getId() # Gets the logins of this user and all its groups.
groups = getattr(self, 'groups', ()) userLogins = self.getLogins()
for id, roles in localRoles.iteritems(): for login, roles in localRoles.iteritems():
if (id != userId) and (id not in groups): continue # Ignore logins not corresponding to this user.
if login not in logins: continue
for role in roles: for role in roles:
if role not in object_roles: continue if role in allowedRoles: return True
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
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -533,7 +533,7 @@ class AbstractWrapper(object):
</table>''') </table>''')
pxView = Px(''' pxView = Px('''
<x var="x=zobj.allows('View', raiseError=True); <x var="x=zobj.allows('read', raiseError=True);
errors=req.get('errors', {}); errors=req.get('errors', {});
layout=zobj.getPageLayout(layoutType); layout=zobj.getPageLayout(layoutType);
phaseObj=zobj.getAppyPhases(currentOnly=True, layoutType='view'); phaseObj=zobj.getAppyPhases(currentOnly=True, layoutType='view');
@ -549,7 +549,7 @@ class AbstractWrapper(object):
</x>''', template=pxTemplate, hook='content') </x>''', template=pxTemplate, hook='content')
pxEdit = Px(''' 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 {}; errors=req.get('errors', None) or {};
layout=zobj.getPageLayout(layoutType); layout=zobj.getPageLayout(layoutType);
cssJs={}; cssJs={};
@ -625,9 +625,9 @@ class AbstractWrapper(object):
def _getParentAttr(klass, attr): def _getParentAttr(klass, attr):
'''Gets value of p_attr on p_klass base classes (if this attr exists). '''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 Scan base classes in the reverse order as Python does. Used by
classmethods m_getWorkflow and m_getCreators below. Scanning base classmethod m_getWorkflow below. Scanning base classes in reverse
classes in reverse order allows user-defined elements to override order allows user-defined elements to override default Appy
default Appy elements.''' elements.'''
i = len(klass.__bases__) - 1 i = len(klass.__bases__) - 1
res = None res = None
while i >= 0: while i >= 0:
@ -643,15 +643,6 @@ class AbstractWrapper(object):
if not res: res = WorkflowAnonymous if not res: res = WorkflowAnonymous
return res 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 @classmethod
def getIndexes(klass, includeDefaults=True): def getIndexes(klass, includeDefaults=True):
'''Returns a dict whose keys are the names of the indexes that are '''Returns a dict whose keys are the names of the indexes that are
@ -823,7 +814,6 @@ class AbstractWrapper(object):
if isField: if isField:
# Link the object to this one # Link the object to this one
appyType.linkObject(self.o, zopeObj) appyType.linkObject(self.o, zopeObj)
zopeObj._appy_managePermissions()
# Call custom initialization # Call custom initialization
if externalData: param = externalData if externalData: param = externalData
else: param = True else: param = True