[gen] Bugfix: slave groups; bugfix: security check for pod fields; security bugfix for pod fields: write access to the field is required for performing any freeze-related action; migration to Appy 0.9.0: dump frozen pod fields on disk; repaired test system for Appy 0.9.0; more explicit error message when using, on some field, a specific write or read permission that is not used in the workflow.
This commit is contained in:
parent
61598b91f7
commit
b4e6333472
|
@ -393,6 +393,7 @@ class File(Field):
|
|||
cfg = zobj.getProductConfig()
|
||||
if isinstance(value, cfg.FileUpload) or isinstance(value, cfg.File):
|
||||
# Cases a, b
|
||||
value.filename = value.filename.replace('/', '-')
|
||||
info.writeFile(self.name, value, dbFolder)
|
||||
elif isinstance(value, UnmarshalledFile):
|
||||
# Case c
|
||||
|
|
|
@ -231,12 +231,12 @@ class UiGroup:
|
|||
|
||||
# PX that renders a group of fields (the group is refered as var "field").
|
||||
pxView = Px('''
|
||||
<x var="tagCss=field.master and ('slave_%s_%s' % \
|
||||
(field.masterName, '_'.join(field.masterValue))) or '';
|
||||
<x var="tagCss=field.master and ('slave*%s*%s' % \
|
||||
(field.masterName, '*'.join(field.masterValue))) or '';
|
||||
widgetCss=field.css_class;
|
||||
groupCss=tagCss and ('%s %s' % (tagCss, widgetCss)) or widgetCss;
|
||||
tagName=field.master and 'slave' or '';
|
||||
tagId='%s_%s' % (zobj.UID(), field.name)">
|
||||
tagId='%s_%s' % (zobj.id, field.name)">
|
||||
|
||||
<!-- Render the group as a fieldset if required -->
|
||||
<fieldset if="field.style == 'fieldset'">
|
||||
|
|
|
@ -278,8 +278,8 @@ class Pod(Field):
|
|||
template = template or self.template[0]
|
||||
format = format or 'odt'
|
||||
# Security check.
|
||||
if not noSecurity and not queryData and \
|
||||
not self.showTemplate(obj, template):
|
||||
if not noSecurity and not queryData:
|
||||
if self.showTemplate and not self.showTemplate(obj, template):
|
||||
raise Exception(self.UNAUTHORIZED)
|
||||
# Return the possibly frozen document (not applicable for query-related
|
||||
# pods).
|
||||
|
@ -445,7 +445,9 @@ class Pod(Field):
|
|||
def getFreezeFormats(self, obj, template=None):
|
||||
'''What are the formats into which the current user may freeze
|
||||
p_template?'''
|
||||
# Manager can always perform freeze actions.
|
||||
# One may have the right to edit the field to freeze anything in it.
|
||||
if not obj.o.mayEdit(self.writePermission): return ()
|
||||
# Manager can perform all freeze actions.
|
||||
template = template or self.template[0]
|
||||
isManager = obj.user.has_role('Manager')
|
||||
if isManager: return self.getAllFormats(template)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import time
|
||||
import os.path, time
|
||||
from appy.fields.file import FileInfo
|
||||
from appy.shared import utils as sutils
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Migrator:
|
||||
|
@ -13,29 +14,58 @@ class Migrator:
|
|||
self.tool = self.app.config.appy()
|
||||
|
||||
@staticmethod
|
||||
def migrateFileFields(obj):
|
||||
'''Ensures all file fields on p_obj are FileInfo instances.'''
|
||||
def migrateBinaryFields(obj):
|
||||
'''Ensures all file and frozen pod fields on p_obj are FileInfo
|
||||
instances.'''
|
||||
migrated = 0 # Count the number of migrated fields
|
||||
for field in obj.fields:
|
||||
if field.type != 'File': continue
|
||||
if field.type == 'File':
|
||||
oldValue = getattr(obj, field.name)
|
||||
if oldValue and not isinstance(oldValue, FileInfo):
|
||||
# A legacy File object. Convert it to a FileInfo instance and
|
||||
# extract the binary to the filesystem.
|
||||
# A legacy File object. Convert it to a FileInfo instance
|
||||
# and extract the binary to the filesystem.
|
||||
setattr(obj, field.name, oldValue)
|
||||
migrated += 1
|
||||
elif field.type == 'Pod':
|
||||
frozen = getattr(obj.o, field.name, None)
|
||||
if frozen:
|
||||
# Dump this file on disk.
|
||||
tempFolder = sutils.getOsTempFolder()
|
||||
fmt = os.path.splitext(frozen.filename)[1][1:]
|
||||
fileName = os.path.join(tempFolder,
|
||||
'%f.%s' % (time.time(), fmt))
|
||||
f = file(fileName, 'wb')
|
||||
if frozen.data.__class__.__name__ == 'Pdata':
|
||||
# The file content is splitted in several chunks.
|
||||
f.write(frozen.data.data)
|
||||
nextPart = frozen.data.next
|
||||
while nextPart:
|
||||
f.write(nextPart.data)
|
||||
nextPart = nextPart.next
|
||||
else:
|
||||
# Only one chunk
|
||||
f.write(frozen.data)
|
||||
f.close()
|
||||
f = file(fileName)
|
||||
field.freeze(obj, template=field.template[0], format=fmt,
|
||||
noSecurity=True, upload=f,
|
||||
freezeOdtOnError=False)
|
||||
f.close()
|
||||
# Remove the legacy in-zodb file object
|
||||
setattr(obj.o, field.name, None)
|
||||
migrated += 1
|
||||
return migrated
|
||||
|
||||
def migrateTo_0_9_0(self):
|
||||
'''Migrates this DB to Appy 0.9.x.'''
|
||||
# Put all binaries to the filesystem
|
||||
tool = self.tool
|
||||
tool.log('Migrating file fields...')
|
||||
context = {'migrate': self.migrateFileFields, 'nb': 0}
|
||||
tool.log('Migrating binary fields...')
|
||||
context = {'migrate': self.migrateBinaryFields, 'nb': 0}
|
||||
for className in tool.o.getAllClassNames():
|
||||
tool.compute(className, context=context, noSecurity=True,
|
||||
expression="ctx['nb'] += ctx['migrate'](obj)")
|
||||
tool.log('Migrated %d File field(s).' % context['nb'])
|
||||
tool.log('Migrated %d binary field(s).' % context['nb'])
|
||||
|
||||
def run(self, force=False):
|
||||
'''Executes a migration when relevant, or do it for sure if p_force is
|
||||
|
|
|
@ -9,11 +9,6 @@ except ImportError:
|
|||
# ------------------------------------------------------------------------------
|
||||
class TestMixin:
|
||||
'''This class is mixed in with any ZopeTestCase.'''
|
||||
def changeUser(self, userId):
|
||||
'''Logs out currently logged user and logs in p_loginName.'''
|
||||
self.logout()
|
||||
self.login(userId)
|
||||
|
||||
def getNonEmptySubModules(self, moduleName):
|
||||
'''Returns the list of sub-modules of p_app that are non-empty.'''
|
||||
res = []
|
||||
|
@ -53,16 +48,7 @@ class TestMixin:
|
|||
for arg in sys.argv:
|
||||
if arg.startswith('[coverage'):
|
||||
return arg[10:].strip(']')
|
||||
return None
|
||||
|
||||
def login(self, name='admin'):
|
||||
user = self.app.acl_users.getUserById(name)
|
||||
newSecurityManager(None, user)
|
||||
|
||||
def logout(self):
|
||||
'''Logs out.'''
|
||||
noSecurityManager()
|
||||
|
||||
return
|
||||
def _setup(self): pass
|
||||
|
||||
# Functions executed before and after every test -------------------------------
|
||||
|
@ -74,7 +60,6 @@ def beforeTest(test):
|
|||
g['appFolder'] = cfg.diskFolder
|
||||
moduleOrClassName = g['test'].name # Not used yet.
|
||||
# Initialize the test
|
||||
test.login('admin')
|
||||
g['t'] = g['test']
|
||||
|
||||
def afterTest(test):
|
||||
|
|
|
@ -1201,6 +1201,11 @@ class BaseMixin:
|
|||
'''Gets, according to the workflow, the roles that are currently granted
|
||||
p_permission on this object.'''
|
||||
state = self.State(name=False)
|
||||
if permission not in state.permissions:
|
||||
wf = self.getWorkflow().__name__
|
||||
raise Exception('Permission "%s" not in permissions dict for ' \
|
||||
'state %s.%s' % \
|
||||
(permission, wf, self.State(name=True)))
|
||||
roles = state.permissions[permission]
|
||||
if roles: return [role.name for role in roles]
|
||||
return ()
|
||||
|
|
|
@ -57,8 +57,8 @@ img { border: 0; vertical-align: middle }
|
|||
.userStrip a:visited { color: #e7e7e7 }
|
||||
.breadcrumb { font-size: 11pt; padding-bottom: 6px }
|
||||
.login { margin: 3px; color: black }
|
||||
input.button { color: #666666; height: 20px; width: 130px;
|
||||
cursor:pointer; font-size: 90%; padding: 1px 0 0 10px;
|
||||
input.button { color: #666666; height: 20px; width: 130px; margin-bottom: 5px;
|
||||
cursor:pointer; font-size: 90%; padding-left: 10px;
|
||||
background-color: white; background-repeat: no-repeat;
|
||||
background-position: 5% 25%; box-shadow: 2px 2px 2px #888888}
|
||||
input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px;
|
||||
|
|
|
@ -647,4 +647,8 @@ class ToolWrapper(AbstractWrapper):
|
|||
except Exception, e:
|
||||
failed.append(startObject)
|
||||
return nb, failed
|
||||
|
||||
def _login(self, login):
|
||||
'''Performs a login programmatically. Used by the test system.'''
|
||||
self.request.user = self.search1('User', noSecurity=True, login=login)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -730,7 +730,7 @@ class AbstractWrapper(object):
|
|||
return res
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s appyobj at %s>' % (self.klass.__name__, id(self))
|
||||
return '<%s at %s>' % (self.klass.__name__, id(self))
|
||||
|
||||
def __cmp__(self, other):
|
||||
if other: return cmp(self.o, other.o)
|
||||
|
|
Loading…
Reference in a new issue