[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
|
@ -199,7 +199,8 @@ class ToolMixin(BaseMixin):
|
|||
|
||||
def getRootClasses(self):
|
||||
'''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):
|
||||
'''Returns the (translated) names of fields of p_className.'''
|
||||
|
@ -265,8 +266,8 @@ class ToolMixin(BaseMixin):
|
|||
p_className.'''
|
||||
appyClass = self.getAppyClass(className)
|
||||
importParams = self.getCreateMeans(appyClass)['import']
|
||||
onElement = importParams['onElement'].__get__('')
|
||||
sortMethod = importParams['sort']
|
||||
onElement = importParams.onElement.__get__('')
|
||||
sortMethod = importParams.sort
|
||||
if sortMethod: sortMethod = sortMethod.__get__('')
|
||||
elems = []
|
||||
importType = self.getAppyType('importPathFor%s' % className)
|
||||
|
@ -280,7 +281,7 @@ class ToolMixin(BaseMixin):
|
|||
elems.append(elemInfo)
|
||||
if sortMethod:
|
||||
elems = sortMethod(elems)
|
||||
return [importParams['headers'], elems]
|
||||
return [importParams.headers, elems]
|
||||
|
||||
def showPortlet(self, context, layoutType):
|
||||
'''When must the portlet be shown?'''
|
||||
|
@ -489,17 +490,13 @@ class ToolMixin(BaseMixin):
|
|||
if wrapper: return zopeClass.wrapperClass
|
||||
else: return zopeClass.wrapperClass.__bases__[-1]
|
||||
|
||||
def getCreateMeans(self, contentTypeOrAppyClass):
|
||||
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
|
||||
can be a Zope content type or a Appy class) can be created
|
||||
(via a web form, by importing external data, etc). Result is a
|
||||
dict whose keys are strings (ie "form", "import"...) and whose
|
||||
values are additional data about the particular mean.'''
|
||||
pythonClass = contentTypeOrAppyClass
|
||||
if isinstance(contentTypeOrAppyClass, basestring):
|
||||
pythonClass = self.getAppyClass(pythonClass)
|
||||
def getCreateMeans(self, klass):
|
||||
'''Gets the different ways objects of p_klass can be created (via a web
|
||||
form, by importing external data, etc). Result is a dict whose keys
|
||||
are strings (ie "form", "import"...) and whose values are additional
|
||||
data about the particular mean.'''
|
||||
res = {}
|
||||
if not pythonClass.__dict__.has_key('create'):
|
||||
if not klass.__dict__.has_key('create'):
|
||||
res['form'] = None
|
||||
# No additional data for this means, which is the default one.
|
||||
else:
|
||||
|
@ -511,23 +508,28 @@ class ToolMixin(BaseMixin):
|
|||
if isinstance(mean, basestring):
|
||||
res[mean] = None
|
||||
else:
|
||||
res[mean.id] = mean.__dict__
|
||||
res[mean.id] = mean
|
||||
else:
|
||||
res[means.id] = means.__dict__
|
||||
res[means.id] = means
|
||||
return res
|
||||
|
||||
def userMaySearch(self, rootClass):
|
||||
'''This method checks if the currently logged user can trigger searches
|
||||
on a given p_rootClass. This is done by calling method "maySearch"
|
||||
on the class. If no such method exists, we return True.'''
|
||||
# When editign a form, one should avoid annoying the user with this.
|
||||
'''May the logged user search among instances of p_rootClass ?'''
|
||||
# When editing a form, one should avoid annoying the user with this.
|
||||
url = self.REQUEST['ACTUAL_URL']
|
||||
if url.endswith('/edit') or url.endswith('/do'): return
|
||||
pythonClass = self.getAppyClass(rootClass)
|
||||
if 'maySearch' in pythonClass.__dict__:
|
||||
return pythonClass.maySearch(self.appy())
|
||||
if 'maySearch' in rootClass.__dict__:
|
||||
return pythonClass.rootClass(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
|
||||
|
||||
def onImportObjects(self):
|
||||
'''This method is called when the user wants to create objects from
|
||||
external data.'''
|
||||
|
@ -737,23 +739,22 @@ class ToolMixin(BaseMixin):
|
|||
obj = self.getObject(objectUid)
|
||||
return obj, fieldName
|
||||
|
||||
def getGroupedSearches(self, className):
|
||||
def getGroupedSearches(self, klass):
|
||||
'''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.
|
||||
Every item representing a search is a dict containing info about a
|
||||
search or about a group of searches.
|
||||
'''
|
||||
appyClass = self.getAppyClass(className)
|
||||
res = []
|
||||
default = None # Also retrieve the default one here.
|
||||
groups = {} # The already encountered groups
|
||||
page = Page('main') # A dummy page required by class UiGroup
|
||||
# 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
|
||||
if hasattr(appyClass, 'getDynamicSearches'):
|
||||
searches += appyClass.getDynamicSearches(self.appy())
|
||||
if hasattr(klass, 'getDynamicSearches'):
|
||||
searches += klass.getDynamicSearches(self.appy())
|
||||
for search in searches:
|
||||
# Create the search descriptor
|
||||
uiSearch = UiSearch(search, className, self)
|
||||
|
@ -792,9 +793,8 @@ class ToolMixin(BaseMixin):
|
|||
if ui: res = UiSearch(res, className, self)
|
||||
return res
|
||||
|
||||
def advancedSearchEnabledFor(self, className):
|
||||
def advancedSearchEnabledFor(self, klass):
|
||||
'''Is advanced search visible for p_klass ?'''
|
||||
klass = self.getAppyClass(className)
|
||||
# By default, advanced search is enabled.
|
||||
if not hasattr(klass, 'searchAdvanced'): return True
|
||||
# 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'
|
||||
# Get the User object from a query in the catalog.
|
||||
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
|
||||
# Precompute some values or this usser for performance reasons
|
||||
# Precompute some values or this user for performance reasons.
|
||||
rq.userRoles = user.getRoles()
|
||||
rq.userLogins = user.getLogins()
|
||||
rq.zopeUser = user.getZopeUser()
|
||||
return user
|
||||
#from AccessControl import getSecurityManager
|
||||
#user = getSecurityManager().getUser()
|
||||
#if not user:
|
||||
# from AccessControl.User import nobody
|
||||
# return nobody
|
||||
#return user
|
||||
|
||||
def getUserLine(self):
|
||||
'''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
|
||||
the URL to edit user info.'''
|
||||
user = self.getUser()
|
||||
userRoles = self.appy().request.userRoles
|
||||
info = [user.title]
|
||||
rolesToShow = [r for r in userRoles if r != 'Authenticated']
|
||||
if rolesToShow:
|
||||
showable = [r for r in user.getRoles() if r != 'Authenticated']
|
||||
if showable:
|
||||
info.append(', '.join([self.translate('role_%s' % r) \
|
||||
for r in rolesToShow]))
|
||||
for r in showable]))
|
||||
# Edit URL for the user.
|
||||
url = None
|
||||
if user.o.mayEdit():
|
||||
|
@ -1134,17 +1131,17 @@ class ToolMixin(BaseMixin):
|
|||
|
||||
def getUserName(self, login=None, normalized=False):
|
||||
'''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
|
||||
names are normalized.'''
|
||||
tool = self.appy()
|
||||
if not login: login = tool.user.login
|
||||
# Manage the special case of an anonymous user.
|
||||
if login == 'Anonymous User':
|
||||
if login == 'anon':
|
||||
name = self.translate('anonymous')
|
||||
if normalized: name = sutils.normalizeString(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)
|
||||
if not user: return login
|
||||
firstName = user.firstName
|
||||
|
|
|
@ -93,9 +93,6 @@ class BaseMixin:
|
|||
# Manage potential link with an initiator object
|
||||
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
|
||||
msg = None # The message to display to the user. It can be set by onEdit
|
||||
if obj.wrapperClass:
|
||||
|
@ -410,7 +407,7 @@ class BaseMixin:
|
|||
return self.goto(tool.getSiteUrl(), msg)
|
||||
# If the user can't access the object anymore, redirect him to the
|
||||
# main site page.
|
||||
if not obj.allows('View'):
|
||||
if not obj.allows('read'):
|
||||
return self.goto(tool.getSiteUrl(), msg)
|
||||
if (buttonClicked == 'save') or saveConfirmed:
|
||||
obj.say(msg)
|
||||
|
@ -484,7 +481,7 @@ class BaseMixin:
|
|||
corresponding Appy wrapper and returns, as XML, the its result.'''
|
||||
self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8')
|
||||
# Check if the user is allowed to consult this object
|
||||
if not self.allows('View'):
|
||||
if not self.allows('read'):
|
||||
return XmlMarshaller().marshall('Unauthorized')
|
||||
if not action:
|
||||
marshaller = XmlMarshaller(rootTag=self.getClass().__name__,
|
||||
|
@ -670,11 +667,6 @@ class BaseMixin:
|
|||
if not allValues: return ''
|
||||
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):
|
||||
'''Are we in debug mode ?'''
|
||||
for arg in sys.argv:
|
||||
|
@ -937,30 +929,6 @@ class BaseMixin:
|
|||
stateName = stateName or self.State()
|
||||
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):
|
||||
'''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
|
||||
|
@ -1105,14 +1073,14 @@ class BaseMixin:
|
|||
|
||||
def mayDelete(self):
|
||||
'''May the currently logged user delete this object?'''
|
||||
res = self.allows('Delete objects')
|
||||
res = self.allows('delete')
|
||||
if not res: return
|
||||
# An additional, user-defined condition, may refine the base permission.
|
||||
appyObj = self.appy()
|
||||
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
|
||||
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
|
||||
field-specific permission.'''
|
||||
res = self.allows(permission)
|
||||
|
@ -1195,6 +1163,12 @@ class BaseMixin:
|
|||
self.reindex()
|
||||
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):
|
||||
'''Returns a wrapper object allowing to manipulate p_self the Appy
|
||||
way.'''
|
||||
|
@ -1284,18 +1258,17 @@ class BaseMixin:
|
|||
'''Returns the list of roles and users that are allowed to view this
|
||||
object. This index value will be used within catalog queries for
|
||||
filtering objects the user is allowed to see.'''
|
||||
res = set()
|
||||
# Get, from the workflow, roles having permission 'View'.
|
||||
for role in self.getProductConfig().rolesForPermissionOn('View', self):
|
||||
res.add(role)
|
||||
# Add users having, locally, this role on this object.
|
||||
localRoles = getattr(self, '__ac_local_roles__', None)
|
||||
if not localRoles: return list(res)
|
||||
# Get, from the workflow, roles having permission 'read'.
|
||||
res = self.getRolesFor('read')
|
||||
# Add users or groups having, locally, this role on this object.
|
||||
localRoles = getattr(self.aq_base, '__ac_local_roles__', None)
|
||||
if not localRoles: return res
|
||||
for id, roles in localRoles.iteritems():
|
||||
for role in roles:
|
||||
if role in res:
|
||||
res.add('user:%s' % id)
|
||||
return list(res)
|
||||
usr = 'user:%s' % id
|
||||
if usr not in res: res.append(usr)
|
||||
return res
|
||||
|
||||
def showState(self):
|
||||
'''Must I show self's current state ?'''
|
||||
|
@ -1326,39 +1299,6 @@ class BaseMixin:
|
|||
res.append((elem, self.translate(self.getWorkflowLabel(elem))))
|
||||
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}
|
||||
def getUrl(self, base=None, mode='view', **kwargs):
|
||||
'''Returns an URL for this object.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue