[gen] Security improvements.
This commit is contained in:
parent
b2dbef2bc4
commit
5c6a7f0f97
|
@ -307,9 +307,9 @@ class Field:
|
||||||
'''When displaying p_obj on a given p_layoutType, must we show this
|
'''When displaying p_obj on a given p_layoutType, must we show this
|
||||||
field?'''
|
field?'''
|
||||||
# Check if the user has the permission to view or edit the field
|
# Check if the user has the permission to view or edit the field
|
||||||
if layoutType == 'edit': perm = self.writePermission
|
perm = (layoutType == 'edit') and self.writePermission or \
|
||||||
else: perm = self.readPermission
|
self.readPermission
|
||||||
if not obj.allows(perm): return False
|
if not obj.allows(perm): return
|
||||||
# Evaluate self.show
|
# Evaluate self.show
|
||||||
if callable(self.show):
|
if callable(self.show):
|
||||||
res = self.callMethod(obj, self.show)
|
res = self.callMethod(obj, self.show)
|
||||||
|
@ -319,7 +319,7 @@ class Field:
|
||||||
if type(res) in sutils.sequenceTypes:
|
if type(res) in sutils.sequenceTypes:
|
||||||
for r in res:
|
for r in res:
|
||||||
if r == layoutType: return True
|
if r == layoutType: return True
|
||||||
return False
|
return
|
||||||
elif res in ('view', 'edit', 'result'):
|
elif res in ('view', 'edit', 'result'):
|
||||||
return res == layoutType
|
return res == layoutType
|
||||||
return bool(res)
|
return bool(res)
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Calendar(Field):
|
||||||
# Month view for a calendar. Called by pxView, and directly from the UI,
|
# Month view for a calendar. Called by pxView, and directly from the UI,
|
||||||
# via Ajax, when the user selects another month.
|
# via Ajax, when the user selects another month.
|
||||||
pxMonthView = Px('''
|
pxMonthView = Px('''
|
||||||
<div var="ajaxHookId=zobj.UID() + field.name;
|
<div var="ajaxHookId=zobj.id + field.name;
|
||||||
month=req['month'];
|
month=req['month'];
|
||||||
monthDayOne=DateTime('%s/01' % month);
|
monthDayOne=DateTime('%s/01' % month);
|
||||||
today=DateTime('00:00');
|
today=DateTime('00:00');
|
||||||
|
@ -27,7 +27,7 @@ class Calendar(Field):
|
||||||
defaultDateMonth=defaultDate.strftime('%Y/%m');
|
defaultDateMonth=defaultDate.strftime('%Y/%m');
|
||||||
previousMonth=field.getSiblingMonth(month, 'previous');
|
previousMonth=field.getSiblingMonth(month, 'previous');
|
||||||
nextMonth=field.getSiblingMonth(month, 'next');
|
nextMonth=field.getSiblingMonth(month, 'next');
|
||||||
mayEdit=zobj.allows(field.writePermission);
|
mayEdit=zobj.mayEdit(field.writePermission);
|
||||||
objUrl=zobj.absolute_url();
|
objUrl=zobj.absolute_url();
|
||||||
startDate=field.getStartDate(zobj);
|
startDate=field.getStartDate(zobj);
|
||||||
endDate=field.getEndDate(zobj);
|
endDate=field.getEndDate(zobj);
|
||||||
|
@ -630,6 +630,8 @@ class Calendar(Field):
|
||||||
or deletion of a calendar event.'''
|
or deletion of a calendar event.'''
|
||||||
rq = obj.REQUEST
|
rq = obj.REQUEST
|
||||||
action = rq['actionType']
|
action = rq['actionType']
|
||||||
|
# Security check
|
||||||
|
obj.mayEdit(self.writePermission, raiseError=True)
|
||||||
# Get the date for this action
|
# Get the date for this action
|
||||||
if action == 'createEvent':
|
if action == 'createEvent':
|
||||||
return self.createEvent(obj, DateTime(rq['day']))
|
return self.createEvent(obj, DateTime(rq['day']))
|
||||||
|
|
|
@ -468,7 +468,7 @@ class Pod(Field):
|
||||||
# What is the action to perform?
|
# What is the action to perform?
|
||||||
action = rq.get('action', 'generate')
|
action = rq.get('action', 'generate')
|
||||||
# Security check.
|
# Security check.
|
||||||
obj.o.allows('read', raiseError=True)
|
obj.o.mayView(self.readPermission, raiseError=True)
|
||||||
# Perform the requested action.
|
# Perform the requested action.
|
||||||
tool = obj.tool.o
|
tool = obj.tool.o
|
||||||
template = rq.get('template')
|
template = rq.get('template')
|
||||||
|
@ -486,7 +486,7 @@ class Pod(Field):
|
||||||
res.writeResponse(rq.RESPONSE)
|
res.writeResponse(rq.RESPONSE)
|
||||||
return
|
return
|
||||||
# Performing any other action requires write access to p_obj.
|
# Performing any other action requires write access to p_obj.
|
||||||
obj.o.allows('write', raiseError=True)
|
obj.o.mayEdit(self.writePermission, raiseError=True)
|
||||||
msg = 'action_done'
|
msg = 'action_done'
|
||||||
if action == 'freeze':
|
if action == 'freeze':
|
||||||
# (Re-)freeze a document in the database.
|
# (Re-)freeze a document in the database.
|
||||||
|
|
|
@ -70,7 +70,7 @@ class Ref(Field):
|
||||||
style=":'%s; %s' % (url(imgName, bg=True), \
|
style=":'%s; %s' % (url(imgName, bg=True), \
|
||||||
ztool.getButtonWidth(label))"/>
|
ztool.getButtonWidth(label))"/>
|
||||||
<!-- Delete several objects -->
|
<!-- Delete several objects -->
|
||||||
<input if="not isBack and field.delete and canWrite"
|
<input if="mayEdit and field.delete"
|
||||||
var2="action='delete'; label=_('object_delete_many')"
|
var2="action='delete'; label=_('object_delete_many')"
|
||||||
type="button" class="button" value=":label"
|
type="button" class="button" value=":label"
|
||||||
onclick=":'onLinkMany(%s,%s)' % (q(action), q(ajaxHookId))"
|
onclick=":'onLinkMany(%s,%s)' % (q(action), q(ajaxHookId))"
|
||||||
|
@ -84,8 +84,7 @@ class Ref(Field):
|
||||||
<table class="noStyle">
|
<table class="noStyle">
|
||||||
<tr>
|
<tr>
|
||||||
<!-- Arrows for moving objects up or down -->
|
<!-- Arrows for moving objects up or down -->
|
||||||
<td if="not isBack and (totalNumber >1) and changeOrder and canWrite \
|
<td if="(totalNumber >1) and changeOrder and not inPickList"
|
||||||
and not inPickList"
|
|
||||||
var2="ajaxBaseCall=navBaseCall.replace('**v**','%s,%s,{%s:%s,%s:%s}'%\
|
var2="ajaxBaseCall=navBaseCall.replace('**v**','%s,%s,{%s:%s,%s:%s}'%\
|
||||||
(q(startNumber), q('doChangeOrder'), q('refObjectUid'),
|
(q(startNumber), q('doChangeOrder'), q('refObjectUid'),
|
||||||
q(tiedUid), q('move'), q('**v**')))">
|
q(tiedUid), q('move'), q('**v**')))">
|
||||||
|
@ -114,7 +113,7 @@ class Ref(Field):
|
||||||
<img src=":url('edit')" title=":_('object_edit')"/></a>
|
<img src=":url('edit')" title=":_('object_edit')"/></a>
|
||||||
</td>
|
</td>
|
||||||
<!-- Delete -->
|
<!-- Delete -->
|
||||||
<td if="not isBack and field.delete and canWrite and tied.o.mayDelete()">
|
<td if="mayEdit and field.delete and tied.o.mayDelete()">
|
||||||
<img class="clickable" title=":_('object_delete')" src=":url('delete')"
|
<img class="clickable" title=":_('object_delete')" src=":url('delete')"
|
||||||
onclick=":'onDeleteObject(%s)' % q(tiedUid)"/>
|
onclick=":'onDeleteObject(%s)' % q(tiedUid)"/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -141,7 +140,7 @@ class Ref(Field):
|
||||||
# Displays the button allowing to add a new object through a Ref field, if
|
# Displays the button allowing to add a new object through a Ref field, if
|
||||||
# it has been declared as addable and if multiplicities allow it.
|
# it has been declared as addable and if multiplicities allow it.
|
||||||
pxAdd = Px('''
|
pxAdd = Px('''
|
||||||
<input if="showPlusIcon and not inPickList" type="button"
|
<input if="mayAdd and not inPickList" type="button"
|
||||||
class="buttonSmall button"
|
class="buttonSmall button"
|
||||||
var2="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.id, field.name, \
|
var2="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.id, field.name, \
|
||||||
field.pageName, 0, totalNumber);
|
field.pageName, 0, totalNumber);
|
||||||
|
@ -163,8 +162,7 @@ class Ref(Field):
|
||||||
# This PX displays, in a cell header from a ref table, icons for sorting the
|
# This PX displays, in a cell header from a ref table, icons for sorting the
|
||||||
# ref field according to the field that corresponds to this column.
|
# ref field according to the field that corresponds to this column.
|
||||||
pxSortIcons = Px('''
|
pxSortIcons = Px('''
|
||||||
<x if="changeOrder and canWrite and ztool.isSortable(refField.name, \
|
<x if="changeOrder and ztool.isSortable(refField.name,tiedClassName,'ref')"
|
||||||
tiedClassName, 'ref')"
|
|
||||||
var2="ajaxBaseCall=navBaseCall.replace('**v**', '%s,%s,{%s:%s,%s:%s}'% \
|
var2="ajaxBaseCall=navBaseCall.replace('**v**', '%s,%s,{%s:%s,%s:%s}'% \
|
||||||
(q(startNumber), q('sort'), q('sortKey'), q(refField.name), \
|
(q(startNumber), q('sort'), q('sortKey'), q(refField.name), \
|
||||||
q('reverse'), q('**v**')))">
|
q('reverse'), q('**v**')))">
|
||||||
|
@ -195,7 +193,7 @@ class Ref(Field):
|
||||||
|
|
||||||
# PX that displays referred objects as a list.
|
# PX that displays referred objects as a list.
|
||||||
pxViewList = Px('''
|
pxViewList = Px('''
|
||||||
<div if="not innerRef or showPlusIcon" style="margin-bottom: 4px">
|
<div if="not innerRef or mayAdd" style="margin-bottom: 4px">
|
||||||
<span if="subLabel" class="discreet">:_(subLabel)</span>
|
<span if="subLabel" class="discreet">:_(subLabel)</span>
|
||||||
(<span class="discreet">:totalNumber</span>)
|
(<span class="discreet">:totalNumber</span>)
|
||||||
<x>:field.pxAdd</x>
|
<x>:field.pxAdd</x>
|
||||||
|
@ -215,7 +213,7 @@ class Ref(Field):
|
||||||
|
|
||||||
<!-- No object is present -->
|
<!-- No object is present -->
|
||||||
<p class="discreet"
|
<p class="discreet"
|
||||||
if="not objects and (innerRef and showPlusIcon)">:_('no_ref')</p>
|
if="not objects and (innerRef and mayAdd)">:_('no_ref')</p>
|
||||||
|
|
||||||
<!-- Linked objects -->
|
<!-- Linked objects -->
|
||||||
<table if="objects" class=":not innerRef and 'list' or ''"
|
<table if="objects" class=":not innerRef and 'list' or ''"
|
||||||
|
@ -242,7 +240,7 @@ class Ref(Field):
|
||||||
class=":loop.tied.odd and 'even' or 'odd'"
|
class=":loop.tied.odd and 'even' or 'odd'"
|
||||||
var2="tiedUid=tied.o.id;
|
var2="tiedUid=tied.o.id;
|
||||||
objectIndex=field.getIndexOf(zobj, tiedUid)|None;
|
objectIndex=field.getIndexOf(zobj, tiedUid)|None;
|
||||||
mayView=tied.allows('read')">
|
mayView=tied.o.mayView()">
|
||||||
<td if="not inPickList and numbered">:field.pxNumber</td>
|
<td if="not inPickList and numbered">:field.pxNumber</td>
|
||||||
<td for="column in columns" width=":column.width" align=":column.align"
|
<td for="column in columns" width=":column.width" align=":column.align"
|
||||||
var2="refField=column.field">
|
var2="refField=column.field">
|
||||||
|
@ -263,15 +261,15 @@ class Ref(Field):
|
||||||
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
|
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
|
||||||
</x>
|
</x>
|
||||||
</td>
|
</td>
|
||||||
<td if="checkboxes and mayView" class="cbCell">
|
<td if="checkboxes" class="cbCell">
|
||||||
<input type="checkbox" name=":ajaxHookId" checked="checked"
|
<input if="mayView" type="checkbox" name=":ajaxHookId" checked="checked"
|
||||||
value=":tiedUid" onclick="toggleRefCb(this)"/>
|
value=":tiedUid" onclick="toggleRefCb(this)"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- Global actions -->
|
<!-- Global actions -->
|
||||||
<div if="canWrite and (totalNumber > 1)"
|
<div if="mayEdit and (totalNumber > 1)"
|
||||||
align=":dright">:field.pxGlobalActions</div>
|
align=":dright">:field.pxGlobalActions</div>
|
||||||
|
|
||||||
<!-- (Bottom) navigation -->
|
<!-- (Bottom) navigation -->
|
||||||
|
@ -288,7 +286,6 @@ class Ref(Field):
|
||||||
<x var="innerRef=False;
|
<x var="innerRef=False;
|
||||||
ajaxHookId=ajaxHookId|'%s_%s_poss' % (zobj.id, field.name);
|
ajaxHookId=ajaxHookId|'%s_%s_poss' % (zobj.id, field.name);
|
||||||
inPickList=True;
|
inPickList=True;
|
||||||
isBack=field.isBack;
|
|
||||||
startNumber=field.getStartNumber('list', req, ajaxHookId);
|
startNumber=field.getStartNumber('list', req, ajaxHookId);
|
||||||
info=field.getPossibleValues(zobj, startNumber=startNumber, \
|
info=field.getPossibleValues(zobj, startNumber=startNumber, \
|
||||||
someObjects=True, removeLinked=True);
|
someObjects=True, removeLinked=True);
|
||||||
|
@ -297,10 +294,10 @@ class Ref(Field):
|
||||||
batchSize=info.batchSize;
|
batchSize=info.batchSize;
|
||||||
batchNumber=len(objects);
|
batchNumber=len(objects);
|
||||||
tiedClassName=tiedClassName|ztool.getPortalType(field.klass);
|
tiedClassName=tiedClassName|ztool.getPortalType(field.klass);
|
||||||
canWrite=canWrite|\
|
mayEdit=mayEdit|\
|
||||||
not field.isBack and zobj.allows(field.writePermission);
|
not field.isBack and zobj.mayEdit(field.writePermission);
|
||||||
mayUnlink=False;
|
mayUnlink=False;
|
||||||
showPlusIcon=False;
|
mayAdd=False;
|
||||||
navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
|
navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
|
||||||
(q(ajaxHookId), q(zobj.absolute_url()), \
|
(q(ajaxHookId), q(zobj.absolute_url()), \
|
||||||
q(field.name), q(innerRef));
|
q(field.name), q(innerRef));
|
||||||
|
@ -360,7 +357,6 @@ class Ref(Field):
|
||||||
linkList=field.link == 'list';
|
linkList=field.link == 'list';
|
||||||
renderAll=req.get('scope') != 'objs';
|
renderAll=req.get('scope') != 'objs';
|
||||||
inPickList=False;
|
inPickList=False;
|
||||||
isBack=field.isBack;
|
|
||||||
startNumber=field.getStartNumber(render, req, ajaxHookId);
|
startNumber=field.getStartNumber(render, req, ajaxHookId);
|
||||||
info=field.getValue(zobj,startNumber=startNumber,someObjects=True);
|
info=field.getValue(zobj,startNumber=startNumber,someObjects=True);
|
||||||
objects=info.objects;
|
objects=info.objects;
|
||||||
|
@ -370,19 +366,18 @@ class Ref(Field):
|
||||||
batchNumber=len(objects);
|
batchNumber=len(objects);
|
||||||
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);
|
mayEdit=not field.isBack and zobj.mayEdit(field.writePermission);
|
||||||
mayUnlink=not isBack and canWrite and \
|
mayUnlink=mayEdit and field.getAttribute(zobj, 'unlink');
|
||||||
field.getAttribute(zobj, 'unlink');
|
mayAdd=mayEdit and field.mayAdd(zobj, checkMayEdit=False);
|
||||||
showPlusIcon=field.mayAdd(zobj);
|
|
||||||
addConfirmMsg=field.addConfirm and \
|
addConfirmMsg=field.addConfirm and \
|
||||||
_('%s_addConfirm' % field.labelId) or '';
|
_('%s_addConfirm' % field.labelId) or '';
|
||||||
navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
|
navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
|
||||||
(q(ajaxHookId), q(zobj.absolute_url()), \
|
(q(ajaxHookId), q(zobj.absolute_url()), \
|
||||||
q(field.name), q(innerRef));
|
q(field.name), q(innerRef));
|
||||||
changeOrder=field.getAttribute(zobj, 'changeOrder');
|
changeOrder=mayEdit and field.getAttribute(zobj, 'changeOrder');
|
||||||
numbered=field.isNumbered(zobj);
|
numbered=field.isNumbered(zobj);
|
||||||
changeNumber=not inPickList and numbered and canWrite and \
|
changeNumber=not inPickList and numbered and changeOrder and \
|
||||||
changeOrder and (totalNumber > 3);
|
(totalNumber > 3);
|
||||||
checkboxesEnabled=field.getAttribute(zobj, 'checkboxes') and \
|
checkboxesEnabled=field.getAttribute(zobj, 'checkboxes') and \
|
||||||
(layoutType != 'cell');
|
(layoutType != 'cell');
|
||||||
checkboxes=checkboxesEnabled and (totalNumber > 1);
|
checkboxes=checkboxesEnabled and (totalNumber > 1);
|
||||||
|
@ -390,7 +385,7 @@ class Ref(Field):
|
||||||
<!-- JS tables storing checkbox statuses if checkboxes are enabled -->
|
<!-- JS tables storing checkbox statuses if checkboxes are enabled -->
|
||||||
<script if="checkboxesEnabled and renderAll"
|
<script if="checkboxesEnabled and renderAll"
|
||||||
type="text/javascript">:field.getCbJsInit(zobj)</script>
|
type="text/javascript">:field.getCbJsInit(zobj)</script>
|
||||||
<div if="linkList and renderAll and canWrite"
|
<div if="linkList and renderAll and mayEdit"
|
||||||
var2="ajaxHookId='%s_%s_poss' % (zobj.id, field.name)"
|
var2="ajaxHookId='%s_%s_poss' % (zobj.id, field.name)"
|
||||||
id=":ajaxHookId">:field.pxViewPickList</div>
|
id=":ajaxHookId">:field.pxViewPickList</div>
|
||||||
<x if="render == 'list'"
|
<x if="render == 'list'"
|
||||||
|
@ -602,10 +597,10 @@ class Ref(Field):
|
||||||
# We add here specific Ref rules for preventing to show the field under
|
# We add here specific Ref rules for preventing to show the field under
|
||||||
# some inappropriate circumstances.
|
# some inappropriate circumstances.
|
||||||
if layoutType == 'edit':
|
if layoutType == 'edit':
|
||||||
if self.mayAdd(obj): return False
|
if self.mayAdd(obj): return
|
||||||
if self.link in (False, 'list'): return False
|
if self.link in (False, 'list'): return
|
||||||
if self.isBack:
|
if self.isBack:
|
||||||
if layoutType == 'edit': return False
|
if layoutType == 'edit': return
|
||||||
else: return getattr(obj.aq_base, self.name, None)
|
else: return getattr(obj.aq_base, self.name, None)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -835,7 +830,7 @@ class Ref(Field):
|
||||||
'''This method links p_value (which can be a list of objects) to p_obj
|
'''This method links p_value (which can be a list of objects) to p_obj
|
||||||
through this Ref field.'''
|
through this Ref field.'''
|
||||||
# Security check
|
# Security check
|
||||||
if not noSecurity: obj.allows(self.writePermission, raiseError=True)
|
if not noSecurity: obj.mayEdit(self.writePermission, raiseError=True)
|
||||||
# p_value can be a list of objects
|
# p_value can be a list of objects
|
||||||
if type(value) in sutils.sequenceTypes:
|
if type(value) in sutils.sequenceTypes:
|
||||||
for v in value: self.linkObject(obj, v, back=back)
|
for v in value: self.linkObject(obj, v, back=back)
|
||||||
|
@ -865,7 +860,7 @@ class Ref(Field):
|
||||||
'''This method unlinks p_value (which can be a list of objects) from
|
'''This method unlinks p_value (which can be a list of objects) from
|
||||||
p_obj through this Ref field.'''
|
p_obj through this Ref field.'''
|
||||||
# Security check
|
# Security check
|
||||||
if not noSecurity: obj.allows(self.writePermission, raiseError=True)
|
if not noSecurity: obj.mayEdit(self.writePermission, raiseError=True)
|
||||||
# p_value can be a list of objects
|
# p_value can be a list of objects
|
||||||
if type(value) in sutils.sequenceTypes:
|
if type(value) in sutils.sequenceTypes:
|
||||||
for v in value: self.unlinkObject(obj, v, back=back)
|
for v in value: self.unlinkObject(obj, v, back=back)
|
||||||
|
@ -916,8 +911,12 @@ class Ref(Field):
|
||||||
if objects:
|
if objects:
|
||||||
self.linkObject(obj, objects)
|
self.linkObject(obj, objects)
|
||||||
|
|
||||||
def mayAdd(self, obj):
|
def mayAdd(self, obj, checkMayEdit=True):
|
||||||
'''May the user create a new referred object from p_obj via this Ref?'''
|
'''May the user create a new referred object from p_obj via this Ref?
|
||||||
|
If p_checkMayEdit is False, it means that the condition of being
|
||||||
|
allowed to edit this Ref field has already been checked somewhere
|
||||||
|
else (it is always required, we just want to avoid checking it
|
||||||
|
twice).'''
|
||||||
# We can't (yet) do that on back references.
|
# We can't (yet) do that on back references.
|
||||||
if self.isBack: return gutils.No('is_back')
|
if self.isBack: return gutils.No('is_back')
|
||||||
# Check if this Ref is addable
|
# Check if this Ref is addable
|
||||||
|
@ -931,7 +930,8 @@ class Ref(Field):
|
||||||
refCount = len(getattr(obj, self.name, ()))
|
refCount = len(getattr(obj, self.name, ()))
|
||||||
if refCount >= self.multiplicity[1]: return gutils.No('max_reached')
|
if refCount >= self.multiplicity[1]: return gutils.No('max_reached')
|
||||||
# May the user edit this Ref field?
|
# May the user edit this Ref field?
|
||||||
if not obj.allows(self.writePermission):
|
if checkMayEdit:
|
||||||
|
if not obj.mayEdit(self.writePermission):
|
||||||
return gutils.No('no_write_perm')
|
return gutils.No('no_write_perm')
|
||||||
# May the user create instances of the referred class?
|
# May the user create instances of the referred class?
|
||||||
if not obj.getTool().userMayCreate(self.klass):
|
if not obj.getTool().userMayCreate(self.klass):
|
||||||
|
@ -943,8 +943,7 @@ class Ref(Field):
|
||||||
m_mayAdd returns False.'''
|
m_mayAdd returns False.'''
|
||||||
may = self.mayAdd(obj)
|
may = self.mayAdd(obj)
|
||||||
if not may:
|
if not may:
|
||||||
from AccessControl import Unauthorized
|
obj.raiseUnauthorized("User can't write Ref field '%s' (%s)." % \
|
||||||
raise Unauthorized("User can't write Ref field '%s' (%s)." % \
|
|
||||||
(self.name, may.msg))
|
(self.name, may.msg))
|
||||||
|
|
||||||
def getCbJsInit(self, obj):
|
def getCbJsInit(self, obj):
|
||||||
|
@ -1070,7 +1069,7 @@ class Ref(Field):
|
||||||
# "link_many", "unlink_many", "delete_many". As a preamble, perform
|
# "link_many", "unlink_many", "delete_many". As a preamble, perform
|
||||||
# a security check once, instead of doing it on every object-level
|
# a security check once, instead of doing it on every object-level
|
||||||
# operation.
|
# operation.
|
||||||
obj.allows(self.writePermission, raiseError=True)
|
obj.mayEdit(self.writePermission, raiseError=True)
|
||||||
# Get the (un-)checked objects from the request.
|
# Get the (un-)checked objects from the request.
|
||||||
uids = rq['targetUid'].strip(',') or ();
|
uids = rq['targetUid'].strip(',') or ();
|
||||||
if uids: uids = uids.split(',')
|
if uids: uids = uids.split(',')
|
||||||
|
|
|
@ -397,7 +397,7 @@ class Transition:
|
||||||
if not obj.isTemporary(): obj.reindex()
|
if not obj.isTemporary(): obj.reindex()
|
||||||
# If we are viewing the object and if the logged user looses the
|
# If we are viewing the object and if the logged user looses the
|
||||||
# permission to view it, redirect the user to its home page.
|
# permission to view it, redirect the user to its home page.
|
||||||
if not obj.allows('read') and \
|
if not obj.mayView() and \
|
||||||
(obj.absolute_url_path() in rq['HTTP_REFERER']):
|
(obj.absolute_url_path() in rq['HTTP_REFERER']):
|
||||||
back = tool.getHomePage()
|
back = tool.getHomePage()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -276,8 +276,7 @@ class BaseMixin:
|
||||||
# used back/forward buttons of its browser...
|
# used back/forward buttons of its browser...
|
||||||
userId = user.login
|
userId = user.login
|
||||||
if (page in self.locks) and (userId != self.locks[page][0]):
|
if (page in self.locks) and (userId != self.locks[page][0]):
|
||||||
from AccessControl import Unauthorized
|
self.raiseUnauthorized('This page is locked.')
|
||||||
raise Unauthorized('This page is locked.')
|
|
||||||
# Set the lock
|
# Set the lock
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
self.locks[page] = (userId, DateTime())
|
self.locks[page] = (userId, DateTime())
|
||||||
|
@ -301,8 +300,7 @@ class BaseMixin:
|
||||||
if not force:
|
if not force:
|
||||||
userId = self.getTool().getUser().login
|
userId = self.getTool().getUser().login
|
||||||
if self.locks[page][0] != userId:
|
if self.locks[page][0] != userId:
|
||||||
from AccessControl import Unauthorized
|
self.raiseUnauthorized('This page was locked by someone else.')
|
||||||
raise Unauthorized('This page was locked by someone else.')
|
|
||||||
# Remove the lock
|
# Remove the lock
|
||||||
del self.locks[page]
|
del self.locks[page]
|
||||||
|
|
||||||
|
@ -444,10 +442,9 @@ class BaseMixin:
|
||||||
# onEdit), redirect to the main site page.
|
# onEdit), redirect to the main site page.
|
||||||
if not getattr(obj.getParentNode().aq_base, obj.id, None):
|
if not getattr(obj.getParentNode().aq_base, obj.id, None):
|
||||||
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 its home
|
||||||
# main site page.
|
# page.
|
||||||
if not obj.allows('read'):
|
if not obj.mayView(): return self.goto(tool.getHomePage(), msg)
|
||||||
return self.goto(tool.getSiteUrl(), msg)
|
|
||||||
if (buttonClicked == 'save') or saveConfirmed:
|
if (buttonClicked == 'save') or saveConfirmed:
|
||||||
obj.say(msg)
|
obj.say(msg)
|
||||||
if isNew and initiator:
|
if isNew and initiator:
|
||||||
|
@ -520,8 +517,7 @@ class BaseMixin:
|
||||||
corresponding Appy wrapper and returns, as XML, its result.'''
|
corresponding Appy wrapper and returns, as XML, 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('read'):
|
if not self.mayView(): 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__,
|
||||||
dumpUnicode=True)
|
dumpUnicode=True)
|
||||||
|
@ -576,6 +572,12 @@ class BaseMixin:
|
||||||
if rq.get('appy', None) == '1': obj = obj.appy()
|
if rq.get('appy', None) == '1': obj = obj.appy()
|
||||||
return getattr(obj, 'on'+action)()
|
return getattr(obj, 'on'+action)()
|
||||||
|
|
||||||
|
def raiseUnauthorized(self, msg=None):
|
||||||
|
'''Raise an error "Unauthorized access".'''
|
||||||
|
from AccessControl import Unauthorized
|
||||||
|
if msg: raise Unauthorized(msg)
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
def rememberPreviousData(self, fields):
|
def rememberPreviousData(self, fields):
|
||||||
'''This method is called before updating an object and remembers, for
|
'''This method is called before updating an object and remembers, for
|
||||||
every historized field, the previous value. Result is a dict
|
every historized field, the previous value. Result is a dict
|
||||||
|
@ -1131,10 +1133,11 @@ class BaseMixin:
|
||||||
return 'main'
|
return 'main'
|
||||||
|
|
||||||
def mayAct(self):
|
def mayAct(self):
|
||||||
'''May the currently logged user see column "actions" for this
|
'''m_mayAct allows to hide the whole set of actions for an object.
|
||||||
object? This can be used for hiding the "edit" icon, for example:
|
Indeed, beyond workflow security, it can be useful to hide controls
|
||||||
when a user may edit only a restricted set of fields on an object,
|
like "edit" icons/buttons. For example, if a user may only edit some
|
||||||
we may avoid showing him the global "edit" icon.'''
|
Ref fields with add=True on an object, when clicking on "edit", he
|
||||||
|
will see an empty edit form.'''
|
||||||
appyObj = self.appy()
|
appyObj = self.appy()
|
||||||
if hasattr(appyObj, 'mayAct'): return appyObj.mayAct()
|
if hasattr(appyObj, 'mayAct'): return appyObj.mayAct()
|
||||||
return True
|
return True
|
||||||
|
@ -1148,14 +1151,34 @@ class BaseMixin:
|
||||||
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
|
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mayEdit(self, permission='write'):
|
def mayEdit(self, permission='write', permOnly=False, raiseError=False):
|
||||||
'''May the currently logged user edit this object? p_perm can be a
|
'''May the currently logged user edit this object? p_permission can be a
|
||||||
field-specific permission.'''
|
field-specific permission. If p_permOnly is True, the specific
|
||||||
res = self.allows(permission)
|
user-defined condition is not evaluated. If p_raiseError is True, if
|
||||||
|
the user may not edit p_self, an error is raised.'''
|
||||||
|
res = self.allows(permission, raiseError=raiseError)
|
||||||
|
if not res: return
|
||||||
|
if permOnly: return res
|
||||||
|
# An additional, user-defined condition, may refine the base permission.
|
||||||
|
appyObj = self.appy()
|
||||||
|
if hasattr(appyObj, 'mayEdit'):
|
||||||
|
res = appyObj.mayEdit()
|
||||||
|
if not res and raiseError: self.raiseUnauthorized()
|
||||||
|
return res
|
||||||
|
return True
|
||||||
|
|
||||||
|
def mayView(self, permission='read', raiseError=False):
|
||||||
|
'''May the currently logged user view this object? p_permission can be a
|
||||||
|
field-specific permission. If p_raiseError is True, if the user may
|
||||||
|
not view p_self, an error is raised.'''
|
||||||
|
res = self.allows(permission, raiseError=raiseError)
|
||||||
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, 'mayEdit'): return appyObj.mayEdit()
|
if hasattr(appyObj, 'mayView'):
|
||||||
|
res = appyObj.mayView()
|
||||||
|
if not res and raiseError: self.raiseUnauthorized()
|
||||||
|
return res
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def onExecuteAction(self):
|
def onExecuteAction(self):
|
||||||
|
@ -1512,14 +1535,12 @@ class BaseMixin:
|
||||||
is retrieved from the request.'''
|
is retrieved from the request.'''
|
||||||
name = self.REQUEST.get('name')
|
name = self.REQUEST.get('name')
|
||||||
if not name: return
|
if not name: return
|
||||||
|
# Security check
|
||||||
if '_img_' not in name:
|
if '_img_' not in name:
|
||||||
appyType = self.getAppyType(name)
|
field = self.getAppyType(name)
|
||||||
else:
|
else:
|
||||||
appyType = self.getAppyType(name.split('_img_')[0])
|
field = self.getAppyType(name.split('_img_')[0])
|
||||||
if (not appyType.isShowable(self, 'view')) and \
|
self.mayView(field.readPermission, raiseError=True)
|
||||||
(not appyType.isShowable(self, 'result')):
|
|
||||||
from zExceptions import NotFound
|
|
||||||
raise NotFound()
|
|
||||||
info = getattr(self.aq_base, name, None)
|
info = getattr(self.aq_base, name, None)
|
||||||
if info:
|
if info:
|
||||||
# Write the file in the HTTP response.
|
# Write the file in the HTTP response.
|
||||||
|
@ -1556,16 +1577,13 @@ class BaseMixin:
|
||||||
def allows(self, permission, raiseError=False):
|
def allows(self, permission, raiseError=False):
|
||||||
'''Has the logged user p_permission on p_self ?'''
|
'''Has the logged user p_permission on p_self ?'''
|
||||||
res = self.getTool().getUser().has_permission(permission, self)
|
res = self.getTool().getUser().has_permission(permission, self)
|
||||||
if not res and raiseError:
|
if not res and raiseError: self.raiseUnauthorized()
|
||||||
from AccessControl import Unauthorized
|
|
||||||
raise Unauthorized
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def isTemporary(self):
|
def isTemporary(self):
|
||||||
'''Is this object temporary ?'''
|
'''Is this object temporary ?'''
|
||||||
parent = self.getParentNode()
|
parent = self.getParentNode()
|
||||||
if not parent: # Is propably being created through code
|
if not parent: return # Is probably being created through code
|
||||||
return False
|
|
||||||
return parent.getId() == 'temp_folder'
|
return parent.getId() == 'temp_folder'
|
||||||
|
|
||||||
def onProcess(self):
|
def onProcess(self):
|
||||||
|
@ -1575,7 +1593,7 @@ class BaseMixin:
|
||||||
|
|
||||||
def onCall(self):
|
def onCall(self):
|
||||||
'''Calls a specific method on the corresponding wrapper.'''
|
'''Calls a specific method on the corresponding wrapper.'''
|
||||||
self.allows('read', raiseError=True)
|
self.mayView(raiseError=True)
|
||||||
method = self.REQUEST['method']
|
method = self.REQUEST['method']
|
||||||
obj = self.appy()
|
obj = self.appy()
|
||||||
return getattr(obj, method)()
|
return getattr(obj, method)()
|
||||||
|
|
|
@ -104,7 +104,7 @@ td.search { padding-top: 8px }
|
||||||
.dropdown a:hover { text-decoration: underline }
|
.dropdown a:hover { text-decoration: underline }
|
||||||
.list { margin-bottom: 3px }
|
.list { margin-bottom: 3px }
|
||||||
.list td, .list th { border: 3px solid #ededed; color: grey;
|
.list td, .list th { border: 3px solid #ededed; color: grey;
|
||||||
padding: 3px 5px 0 5px }
|
padding: 3px 5px 3px 5px }
|
||||||
.list th { background-color: #e5e5e5; font-style: italic; font-weight: normal }
|
.list th { background-color: #e5e5e5; font-style: italic; font-weight: normal }
|
||||||
.grid th { font-style: italic; font-weight: normal;
|
.grid th { font-style: italic; font-weight: normal;
|
||||||
border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
|
border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
|
||||||
|
|
|
@ -302,7 +302,8 @@ class ToolWrapper(AbstractWrapper):
|
||||||
# Show on query list or grid, the field content for a given object.
|
# Show on query list or grid, the field content for a given object.
|
||||||
pxQueryField = Px('''
|
pxQueryField = Px('''
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<x if="field.name == 'title'"
|
<x if="field.name == 'title'">
|
||||||
|
<x if="mayView"
|
||||||
var2="navInfo='search.%s.%s.%d.%d' % \
|
var2="navInfo='search.%s.%s.%d.%d' % \
|
||||||
(className, searchName, startNumber+currentNumber, totalNumber);
|
(className, searchName, startNumber+currentNumber, totalNumber);
|
||||||
cssClass=zobj.getCssFor('title')">
|
cssClass=zobj.getCssFor('title')">
|
||||||
|
@ -328,7 +329,7 @@ class ToolWrapper(AbstractWrapper):
|
||||||
<!-- Delete -->
|
<!-- Delete -->
|
||||||
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
|
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
|
||||||
title=":_('object_delete')"
|
title=":_('object_delete')"
|
||||||
onClick=":'onDeleteObject(%s)' % q(zobj.UID())"/>
|
onClick=":'onDeleteObject(%s)' % q(zobj.id)"/>
|
||||||
</td>
|
</td>
|
||||||
<!-- Workflow transitions -->
|
<!-- Workflow transitions -->
|
||||||
<td if="zobj.showTransitions('result')"
|
<td if="zobj.showTransitions('result')"
|
||||||
|
@ -337,8 +338,13 @@ class ToolWrapper(AbstractWrapper):
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</x>
|
</x>
|
||||||
|
<x if="not mayView">
|
||||||
|
<img src=":url('fake')" style="margin-right: 5px"/>
|
||||||
|
<x>:_('unauthorized')</x>
|
||||||
|
</x>
|
||||||
|
</x>
|
||||||
<!-- Any other field -->
|
<!-- Any other field -->
|
||||||
<x if="field.name != 'title'">
|
<x if="(field.name != 'title') and mayView">
|
||||||
<x var="layoutType='cell'; innerRef=True"
|
<x var="layoutType='cell'; innerRef=True"
|
||||||
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
|
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
|
||||||
</x>''')
|
</x>''')
|
||||||
|
@ -361,7 +367,7 @@ class ToolWrapper(AbstractWrapper):
|
||||||
<!-- Results -->
|
<!-- Results -->
|
||||||
<tr for="zobj in zobjects" id="query_row" valign="top"
|
<tr for="zobj in zobjects" id="query_row" valign="top"
|
||||||
var2="currentNumber=currentNumber + 1;
|
var2="currentNumber=currentNumber + 1;
|
||||||
obj=zobj.appy()"
|
obj=zobj.appy(); mayView=zobj.mayView()"
|
||||||
class=":loop.zobj.odd and 'even' or 'odd'">
|
class=":loop.zobj.odd and 'even' or 'odd'">
|
||||||
<td for="column in columns"
|
<td for="column in columns"
|
||||||
var2="field=column.field" id=":'field_%s' % field.name"
|
var2="field=column.field" id=":'field_%s' % field.name"
|
||||||
|
@ -378,7 +384,8 @@ class ToolWrapper(AbstractWrapper):
|
||||||
rows=ztool.splitList(zobjects, cols)">
|
rows=ztool.splitList(zobjects, cols)">
|
||||||
<tr for="row in rows" valign="middle">
|
<tr for="row in rows" valign="middle">
|
||||||
<td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
|
<td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
|
||||||
style="padding-top: 25px" var2="obj=zobj.appy()">
|
style="padding-top: 25px"
|
||||||
|
var2="obj=zobj.appy(); mayView=zobj.mayView()">
|
||||||
<x var="currentNumber=currentNumber + 1"
|
<x var="currentNumber=currentNumber + 1"
|
||||||
for="column in columns"
|
for="column in columns"
|
||||||
var2="field=column.field">:tool.pxQueryField</x>
|
var2="field=column.field">:tool.pxQueryField</x>
|
||||||
|
|
|
@ -454,6 +454,7 @@ class AbstractWrapper(object):
|
||||||
var="previousPage=phaseObj.getPreviousPage(page)[0];
|
var="previousPage=phaseObj.getPreviousPage(page)[0];
|
||||||
nextPage=phaseObj.getNextPage(page)[0];
|
nextPage=phaseObj.getNextPage(page)[0];
|
||||||
isEdit=layoutType == 'edit';
|
isEdit=layoutType == 'edit';
|
||||||
|
mayAct=not isEdit and zobj.mayAct();
|
||||||
pageInfo=phaseObj.pagesInfo[page]">
|
pageInfo=phaseObj.pagesInfo[page]">
|
||||||
<tr valign="top">
|
<tr valign="top">
|
||||||
<!-- Refresh -->
|
<!-- Refresh -->
|
||||||
|
@ -486,7 +487,6 @@ class AbstractWrapper(object):
|
||||||
style=":'%s; %s' % (url('save', bg=True), \
|
style=":'%s; %s' % (url('save', bg=True), \
|
||||||
ztool.getButtonWidth(label))" />
|
ztool.getButtonWidth(label))" />
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Cancel -->
|
<!-- Cancel -->
|
||||||
<td if="isEdit and pageInfo.showCancel">
|
<td if="isEdit and pageInfo.showCancel">
|
||||||
<input type="button" class="button" onClick="submitAppyForm('cancel')"
|
<input type="button" class="button" onClick="submitAppyForm('cancel')"
|
||||||
|
@ -494,11 +494,10 @@ class AbstractWrapper(object):
|
||||||
style=":'%s; %s' % (url('cancel', bg=True), \
|
style=":'%s; %s' % (url('cancel', bg=True), \
|
||||||
ztool.getButtonWidth(label))"/>
|
ztool.getButtonWidth(label))"/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td if="not isEdit"
|
<td if="not isEdit"
|
||||||
var2="locked=zobj.isLocked(user, page);
|
var2="locked=zobj.isLocked(user, page);
|
||||||
editable=pageInfo.showOnEdit and pageInfo.showEdit and \
|
editable=pageInfo.showOnEdit and pageInfo.showEdit and \
|
||||||
zobj.mayEdit()">
|
mayAct and zobj.mayEdit()">
|
||||||
|
|
||||||
<!-- Edit -->
|
<!-- Edit -->
|
||||||
<input type="button" class="button" if="editable and not locked"
|
<input type="button" class="button" if="editable and not locked"
|
||||||
|
@ -540,7 +539,8 @@ class AbstractWrapper(object):
|
||||||
|
|
||||||
<!-- Workflow transitions -->
|
<!-- Workflow transitions -->
|
||||||
<td var="targetObj=zobj; buttonsMode='normal'"
|
<td var="targetObj=zobj; buttonsMode='normal'"
|
||||||
if="targetObj.showTransitions(layoutType)">:obj.pxTransitions</td>
|
if="mayAct and \
|
||||||
|
targetObj.showTransitions(layoutType)">:obj.pxTransitions</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>''')
|
</table>''')
|
||||||
|
|
||||||
|
@ -554,7 +554,7 @@ class AbstractWrapper(object):
|
||||||
</table>''')
|
</table>''')
|
||||||
|
|
||||||
pxView = Px('''
|
pxView = Px('''
|
||||||
<x var="x=zobj.allows('read', raiseError=True);
|
<x var="x=zobj.mayView(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');
|
||||||
|
@ -570,7 +570,7 @@ class AbstractWrapper(object):
|
||||||
</x>''', template=pxTemplate, hook='content')
|
</x>''', template=pxTemplate, hook='content')
|
||||||
|
|
||||||
pxEdit = Px('''
|
pxEdit = Px('''
|
||||||
<x var="x=zobj.allows('write', raiseError=True);
|
<x var="x=zobj.mayEdit(raiseError=True, permOnly=zobj.isTemporary());
|
||||||
errors=req.get('errors', {});
|
errors=req.get('errors', {});
|
||||||
layout=zobj.getPageLayout(layoutType);
|
layout=zobj.getPageLayout(layoutType);
|
||||||
cssJs={};
|
cssJs={};
|
||||||
|
|
Loading…
Reference in a new issue