[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:
Gaetan Delannay 2014-05-14 15:10:41 +02:00
parent 61598b91f7
commit b4e6333472
9 changed files with 66 additions and 39 deletions

View file

@ -393,6 +393,7 @@ class File(Field):
cfg = zobj.getProductConfig() cfg = zobj.getProductConfig()
if isinstance(value, cfg.FileUpload) or isinstance(value, cfg.File): if isinstance(value, cfg.FileUpload) or isinstance(value, cfg.File):
# Cases a, b # Cases a, b
value.filename = value.filename.replace('/', '-')
info.writeFile(self.name, value, dbFolder) info.writeFile(self.name, value, dbFolder)
elif isinstance(value, UnmarshalledFile): elif isinstance(value, UnmarshalledFile):
# Case c # Case c

View file

@ -231,12 +231,12 @@ class UiGroup:
# PX that renders a group of fields (the group is refered as var "field"). # PX that renders a group of fields (the group is refered as var "field").
pxView = Px(''' pxView = Px('''
<x var="tagCss=field.master and ('slave_%s_%s' % \ <x var="tagCss=field.master and ('slave*%s*%s' % \
(field.masterName, '_'.join(field.masterValue))) or ''; (field.masterName, '*'.join(field.masterValue))) or '';
widgetCss=field.css_class; widgetCss=field.css_class;
groupCss=tagCss and ('%s %s' % (tagCss, widgetCss)) or widgetCss; groupCss=tagCss and ('%s %s' % (tagCss, widgetCss)) or widgetCss;
tagName=field.master and 'slave' or ''; 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 --> <!-- Render the group as a fieldset if required -->
<fieldset if="field.style == 'fieldset'"> <fieldset if="field.style == 'fieldset'">

View file

@ -278,9 +278,9 @@ class Pod(Field):
template = template or self.template[0] template = template or self.template[0]
format = format or 'odt' format = format or 'odt'
# Security check. # Security check.
if not noSecurity and not queryData and \ if not noSecurity and not queryData:
not self.showTemplate(obj, template): if self.showTemplate and not self.showTemplate(obj, template):
raise Exception(self.UNAUTHORIZED) raise Exception(self.UNAUTHORIZED)
# Return the possibly frozen document (not applicable for query-related # Return the possibly frozen document (not applicable for query-related
# pods). # pods).
if not queryData: if not queryData:
@ -445,7 +445,9 @@ class Pod(Field):
def getFreezeFormats(self, obj, template=None): def getFreezeFormats(self, obj, template=None):
'''What are the formats into which the current user may freeze '''What are the formats into which the current user may freeze
p_template?''' 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] template = template or self.template[0]
isManager = obj.user.has_role('Manager') isManager = obj.user.has_role('Manager')
if isManager: return self.getAllFormats(template) if isManager: return self.getAllFormats(template)

View file

@ -1,6 +1,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import time import os.path, time
from appy.fields.file import FileInfo from appy.fields.file import FileInfo
from appy.shared import utils as sutils
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Migrator: class Migrator:
@ -13,29 +14,58 @@ class Migrator:
self.tool = self.app.config.appy() self.tool = self.app.config.appy()
@staticmethod @staticmethod
def migrateFileFields(obj): def migrateBinaryFields(obj):
'''Ensures all file fields on p_obj are FileInfo instances.''' '''Ensures all file and frozen pod fields on p_obj are FileInfo
instances.'''
migrated = 0 # Count the number of migrated fields migrated = 0 # Count the number of migrated fields
for field in obj.fields: for field in obj.fields:
if field.type != 'File': continue if field.type == 'File':
oldValue = getattr(obj, field.name) oldValue = getattr(obj, field.name)
if oldValue and not isinstance(oldValue, FileInfo): if oldValue and not isinstance(oldValue, FileInfo):
# A legacy File object. Convert it to a FileInfo instance and # A legacy File object. Convert it to a FileInfo instance
# extract the binary to the filesystem. # and extract the binary to the filesystem.
setattr(obj, field.name, oldValue) setattr(obj, field.name, oldValue)
migrated += 1 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 return migrated
def migrateTo_0_9_0(self): def migrateTo_0_9_0(self):
'''Migrates this DB to Appy 0.9.x.''' '''Migrates this DB to Appy 0.9.x.'''
# Put all binaries to the filesystem # Put all binaries to the filesystem
tool = self.tool tool = self.tool
tool.log('Migrating file fields...') tool.log('Migrating binary fields...')
context = {'migrate': self.migrateFileFields, 'nb': 0} context = {'migrate': self.migrateBinaryFields, 'nb': 0}
for className in tool.o.getAllClassNames(): for className in tool.o.getAllClassNames():
tool.compute(className, context=context, noSecurity=True, tool.compute(className, context=context, noSecurity=True,
expression="ctx['nb'] += ctx['migrate'](obj)") 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): def run(self, force=False):
'''Executes a migration when relevant, or do it for sure if p_force is '''Executes a migration when relevant, or do it for sure if p_force is

View file

@ -9,11 +9,6 @@ except ImportError:
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class TestMixin: class TestMixin:
'''This class is mixed in with any ZopeTestCase.''' '''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): def getNonEmptySubModules(self, moduleName):
'''Returns the list of sub-modules of p_app that are non-empty.''' '''Returns the list of sub-modules of p_app that are non-empty.'''
res = [] res = []
@ -53,16 +48,7 @@ class TestMixin:
for arg in sys.argv: for arg in sys.argv:
if arg.startswith('[coverage'): if arg.startswith('[coverage'):
return arg[10:].strip(']') return arg[10:].strip(']')
return None return
def login(self, name='admin'):
user = self.app.acl_users.getUserById(name)
newSecurityManager(None, user)
def logout(self):
'''Logs out.'''
noSecurityManager()
def _setup(self): pass def _setup(self): pass
# Functions executed before and after every test ------------------------------- # Functions executed before and after every test -------------------------------
@ -74,7 +60,6 @@ def beforeTest(test):
g['appFolder'] = cfg.diskFolder g['appFolder'] = cfg.diskFolder
moduleOrClassName = g['test'].name # Not used yet. moduleOrClassName = g['test'].name # Not used yet.
# Initialize the test # Initialize the test
test.login('admin')
g['t'] = g['test'] g['t'] = g['test']
def afterTest(test): def afterTest(test):

View file

@ -1201,6 +1201,11 @@ class BaseMixin:
'''Gets, according to the workflow, the roles that are currently granted '''Gets, according to the workflow, the roles that are currently granted
p_permission on this object.''' p_permission on this object.'''
state = self.State(name=False) 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] roles = state.permissions[permission]
if roles: return [role.name for role in roles] if roles: return [role.name for role in roles]
return () return ()

View file

@ -57,8 +57,8 @@ img { border: 0; vertical-align: middle }
.userStrip a:visited { color: #e7e7e7 } .userStrip a:visited { color: #e7e7e7 }
.breadcrumb { font-size: 11pt; padding-bottom: 6px } .breadcrumb { font-size: 11pt; padding-bottom: 6px }
.login { margin: 3px; color: black } .login { margin: 3px; color: black }
input.button { color: #666666; height: 20px; width: 130px; input.button { color: #666666; height: 20px; width: 130px; margin-bottom: 5px;
cursor:pointer; font-size: 90%; padding: 1px 0 0 10px; cursor:pointer; font-size: 90%; padding-left: 10px;
background-color: white; background-repeat: no-repeat; background-color: white; background-repeat: no-repeat;
background-position: 5% 25%; box-shadow: 2px 2px 2px #888888} background-position: 5% 25%; box-shadow: 2px 2px 2px #888888}
input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px; input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px;

View file

@ -647,4 +647,8 @@ class ToolWrapper(AbstractWrapper):
except Exception, e: except Exception, e:
failed.append(startObject) failed.append(startObject)
return nb, failed 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)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -730,7 +730,7 @@ class AbstractWrapper(object):
return res return res
def __repr__(self): 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): def __cmp__(self, other):
if other: return cmp(self.o, other.o) if other: return cmp(self.o, other.o)