Commit 7518e634 authored by Jérome Perrin's avatar Jérome Perrin

Merge remote-tracking branch 'upstream/master' into zope4py2

parents a274cdfd b39b5e7b
"""Check consistency of an accounting transaction.
This verifies the constraints defined in constraints and also "temporary" constraints,
such as "client is validated" or "accounting period is open" that are currently defined
in workflow script.
This is intentded to be used in custom scripts creating accounting transactions and
validating them.
"""
context.Base_checkConsistency()
accounting_workflow = context.getPortalObject().portal_workflow.accounting_workflow
accounting_workflow.script_validateTransactionLines(
{
'object': context,
'kwargs': {},
'transition': accounting_workflow.transition_deliver_action
})
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingTransaction_checkConsistency</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -1147,6 +1147,33 @@ class TestTransactionValidation(AccountingTestCase): ...@@ -1147,6 +1147,33 @@ class TestTransactionValidation(AccountingTestCase):
self.portal.portal_workflow.doActionFor(accounting_transaction, self.portal.portal_workflow.doActionFor(accounting_transaction,
'stop_action') 'stop_action')
def test_AccountingTransaction_checkConsistency(self):
accounting_transaction = self._makeOne(
portal_type='Accounting Transaction',
start_date=DateTime('2008/12/31'),
destination_section_value=self.organisation_module.supplier,
lines=(dict(id='line_with_wrong_quantity',
source_value=self.account_module.goods_purchase,
source_debit=400),
dict(source_value=self.account_module.receivable,
source_credit=500)))
self.assertRaisesRegexp(
ValidationFailed,
'Transaction is not balanced',
accounting_transaction.AccountingTransaction_checkConsistency,
)
accounting_transaction.line_with_wrong_quantity.setSourceDebit(500)
self.assertRaisesRegexp(
ValidationFailed,
'Date is not in a started Accounting Period',
accounting_transaction.AccountingTransaction_checkConsistency,
)
accounting_transaction.setStartDate(DateTime('2007/11/11'))
accounting_transaction.AccountingTransaction_checkConsistency()
class TestClosingPeriod(AccountingTestCase): class TestClosingPeriod(AccountingTestCase):
"""Various tests for closing the period. """Various tests for closing the period.
......
...@@ -28,7 +28,7 @@ transaction.Base_checkConsistency() ...@@ -28,7 +28,7 @@ transaction.Base_checkConsistency()
skip_period_validation = state_change['kwargs'].get( skip_period_validation = state_change['kwargs'].get(
'skip_period_validation', 0) 'skip_period_validation', 0)
transition = state_change['transition'] transition = state_change['transition']
if transition.id in ('plan_action', 'confirm_action') : if transition.getReference() in ('plan_action', 'confirm_action') :
skip_period_validation = 1 skip_period_validation = 1
source_section = transaction.getSourceSectionValue( source_section = transaction.getSourceSectionValue(
......
...@@ -45,7 +45,7 @@ class InternetMessagePost(Item, MailMessageMixin): ...@@ -45,7 +45,7 @@ class InternetMessagePost(Item, MailMessageMixin):
def _getMessage(self): def _getMessage(self):
return email.message_from_string(self.getData()) return email.message_from_string(self.getData())
security.declareProtected(Permissions.AccessContentsInformation, 'stripMessageId')
def stripMessageId(self, message_id): def stripMessageId(self, message_id):
""" """
In rfc5322 headers, message-ids may follow the syntax "<msg-id>" in In rfc5322 headers, message-ids may follow the syntax "<msg-id>" in
...@@ -59,11 +59,10 @@ class InternetMessagePost(Item, MailMessageMixin): ...@@ -59,11 +59,10 @@ class InternetMessagePost(Item, MailMessageMixin):
message_id = message_id[:-1] message_id = message_id[:-1]
return message_id return message_id
security.declareProtected(Permissions.AccessContentsInformation, 'getReference')
def getReference(self): def getReference(self):
return self.stripMessageId(self.getSourceReference()) return self.stripMessageId(self.getSourceReference())
def _setReference(self, value): def _setReference(self, value):
""" """
Raise if given value is different from current value, Raise if given value is different from current value,
......
...@@ -62,6 +62,7 @@ class OpenOrderLine(SupplyLine): ...@@ -62,6 +62,7 @@ class OpenOrderLine(SupplyLine):
, PropertySheet.Comment , PropertySheet.Comment
) )
security.declareProtected(Permissions.AccessContentsInformation, 'getTotalQuantity')
def getTotalQuantity(self, default=0): def getTotalQuantity(self, default=0):
"""Returns the total quantity for this open order line. """Returns the total quantity for this open order line.
If the order line contains cells, the total quantity of cells are If the order line contains cells, the total quantity of cells are
...@@ -72,6 +73,7 @@ class OpenOrderLine(SupplyLine): ...@@ -72,6 +73,7 @@ class OpenOrderLine(SupplyLine):
self.getCellValueList(base_id='path')]) self.getCellValueList(base_id='path')])
return self.getQuantity(default) return self.getQuantity(default)
security.declareProtected(Permissions.AccessContentsInformation, 'getTotalPrice')
def getTotalPrice(self): def getTotalPrice(self):
"""Returns the total price for this open order line. """Returns the total price for this open order line.
If the order line contains cells, the total price of cells are If the order line contains cells, the total price of cells are
......
from DateTime import DateTime from DateTime import DateTime
appcache_reference = context.getLayoutProperty("configuration_manifest_url") appcache_reference = context.getLayoutProperty("configuration_manifest_url")
getDocumentValue = context.getDocumentValue
error_list = [] error_list = []
if appcache_reference: if appcache_reference:
appcache_manifest = getDocumentValue(appcache_reference) appcache_manifest = context.getDocumentValue(appcache_reference)
if not appcache_manifest: if not appcache_manifest:
return ['Error: Web Site %s references a non existant appcache %s' % (context.getRelativeUrl(),appcache_reference)] return ['Error: Web Site %s references a non existant appcache %s' % (context.getRelativeUrl(),appcache_reference)]
url_list = context.Base_getListFileFromAppcache() url_list = [url for url in context.Base_getListFileFromAppcache() if url]
# Check that the manifest is newer than all cached resources. # Check that the manifest is newer than all cached resources.
appcache_manifest_modification_date = appcache_manifest.getObject().getModificationDate() appcache_manifest_modification_date = appcache_manifest.getObject().getModificationDate()
for url in url_list:
if url: if url_list:
referenced_document = getDocumentValue(url) for referenced_document in context.getDocumentValueList(reference=url_list):
if referenced_document is not None and ( if referenced_document.getModificationDate() > appcache_manifest_modification_date:
referenced_document.getModificationDate() >
appcache_manifest_modification_date):
error_list.append( error_list.append(
"Document {} is newer than cache manifest".format(url)) "Document {} is newer than cache manifest".format(referenced_document.getReference()))
if error_list and fixit: if error_list and fixit:
text_list = appcache_manifest.getTextContent().split('\n') text_list = appcache_manifest.getTextContent().split('\n')
......
...@@ -13,12 +13,18 @@ Base_translateString = context.Base_translateString ...@@ -13,12 +13,18 @@ Base_translateString = context.Base_translateString
translatable_message_set = set([]) translatable_message_set = set([])
web_page_reference_list = context.Base_getTranslationSourceFileList(only_html=1) web_page_reference_list = context.Base_getTranslationSourceFileList(only_html=1)
web_page_by_reference = {}
if web_page_reference_list:
web_page_list = [
b.getObject() for b in
context.getDocumentValueList(reference=web_page_reference_list)]
web_page_by_reference = {wp.getReference(): wp.getTextContent() for wp in web_page_list}
for web_page_reference in web_page_reference_list: for web_page_reference in web_page_reference_list:
# Web pages can be in web page module ... # Web pages can be in web page module ...
web_page = context.getDocumentValue(web_page_reference) web_page_text_content = web_page_by_reference.get(web_page_reference)
if web_page is not None: if web_page_text_content is None:
web_page_text_content = web_page.getTextContent()
else:
# ... or in skin folders # ... or in skin folders
web_page = context.restrictedTraverse(web_page_reference, None) web_page = context.restrictedTraverse(web_page_reference, None)
if web_page is not None and hasattr(web_page, 'PrincipiaSearchSource'): if web_page is not None and hasattr(web_page, 'PrincipiaSearchSource'):
......
...@@ -66,6 +66,7 @@ class FTPConnector(XMLObject): ...@@ -66,6 +66,7 @@ class FTPConnector(XMLObject):
# XXX Must manage in the future ftp and ftps protocol # XXX Must manage in the future ftp and ftps protocol
raise NotImplementedError("Protocol %s is not yet implemented" %(self.getUrlProtocol(),)) raise NotImplementedError("Protocol %s is not yet implemented" %(self.getUrlProtocol(),))
security.declareProtected(Permissions.AccessContentsInformation, 'renameFile')
def renameFile(self, old_path, new_path): def renameFile(self, old_path, new_path):
""" Move a file """ """ Move a file """
conn = self.getConnection() conn = self.getConnection()
...@@ -74,6 +75,7 @@ class FTPConnector(XMLObject): ...@@ -74,6 +75,7 @@ class FTPConnector(XMLObject):
finally: finally:
conn.logout() conn.logout()
security.declareProtected(Permissions.AccessContentsInformation, 'removeFile')
def removeFile(self, filepath): def removeFile(self, filepath):
"""Delete the file""" """Delete the file"""
conn = self.getConnection() conn = self.getConnection()
...@@ -82,6 +84,7 @@ class FTPConnector(XMLObject): ...@@ -82,6 +84,7 @@ class FTPConnector(XMLObject):
finally: finally:
conn.logout() conn.logout()
security.declareProtected(Permissions.AccessContentsInformation, 'listFiles')
def listFiles(self, path=".", sort_on=None): def listFiles(self, path=".", sort_on=None):
""" List file of a directory """ """ List file of a directory """
conn = self.getConnection() conn = self.getConnection()
...@@ -90,6 +93,7 @@ class FTPConnector(XMLObject): ...@@ -90,6 +93,7 @@ class FTPConnector(XMLObject):
finally: finally:
conn.logout() conn.logout()
security.declareProtected(Permissions.AccessContentsInformation, 'getFile')
def getFile(self, filepath, binary=True): def getFile(self, filepath, binary=True):
""" Try to get a file on the remote server """ """ Try to get a file on the remote server """
conn = self.getConnection() conn = self.getConnection()
...@@ -101,6 +105,7 @@ class FTPConnector(XMLObject): ...@@ -101,6 +105,7 @@ class FTPConnector(XMLObject):
finally: finally:
conn.logout() conn.logout()
security.declareProtected(Permissions.AccessContentsInformation, 'putFile')
def putFile(self, filename, data, remotepath='.', confirm=True): def putFile(self, filename, data, remotepath='.', confirm=True):
""" Send file to the remote server """ """ Send file to the remote server """
conn = self.getConnection() conn = self.getConnection()
...@@ -125,6 +130,7 @@ class FTPConnector(XMLObject): ...@@ -125,6 +130,7 @@ class FTPConnector(XMLObject):
finally: finally:
conn.logout() conn.logout()
security.declareProtected(Permissions.AccessContentsInformation, 'createDirectory')
def createDirectory(self, path, mode=0o777): def createDirectory(self, path, mode=0o777):
"""Create a directory `path`, with file mode `mode`. """Create a directory `path`, with file mode `mode`.
...@@ -136,6 +142,7 @@ class FTPConnector(XMLObject): ...@@ -136,6 +142,7 @@ class FTPConnector(XMLObject):
finally: finally:
conn.logout() conn.logout()
security.declareProtected(Permissions.AccessContentsInformation, 'removeDirectory')
def removeDirectory(self, path): def removeDirectory(self, path):
"""Create a directory `path`, with file mode `mode`. """Create a directory `path`, with file mode `mode`.
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>text</string> </key> <key> <string>text</string> </key>
<value> <string>string:${object_url}/activeSense</string> </value> <value> <string>string:${object_url}/Alarm_activeSense</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
from Products.ERP5Type.Message import translateString
context.activeSense()
return context.Base_redirect(
form_id,
keep_items={'portal_status_message': translateString('Active Sense Called.')})
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>form_id=\'\', **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_activeSense</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
</item> </item>
<item> <item>
<key> <string>display_width</string> </key> <key> <string>display_width</string> </key>
<value> <int>20</int> </value> <value> <int>80</int> </value>
</item> </item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>editable</string> </key>
...@@ -227,6 +227,10 @@ ...@@ -227,6 +227,10 @@
<key> <string>hidden</string> </key> <key> <string>hidden</string> </key>
<value> <int>0</int> </value> <value> <int>0</int> </value>
</item> </item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item> <item>
<key> <string>max_length</string> </key> <key> <string>max_length</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
......
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
</item> </item>
<item> <item>
<key> <string>display_width</string> </key> <key> <string>display_width</string> </key>
<value> <int>20</int> </value> <value> <int>80</int> </value>
</item> </item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>editable</string> </key>
......
...@@ -6,6 +6,14 @@ ...@@ -6,6 +6,14 @@
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <value>
......
...@@ -6,6 +6,14 @@ ...@@ -6,6 +6,14 @@
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <value>
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
import unittest
import sys
import traceback
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
"""Test for ERP5 callables, is ERP5 documents to be used in portal_skins
or portal_workflow
"""
class TestERP5PythonScript(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return 'erp5_core',
def afterSetUp(self):
folder = self.portal.portal_skins.custom
folder.manage_addProduct['ERP5'].addPythonScriptThroughZMI(
id=self.id()
)
self.script = folder.get(self.id())
self.commit()
def beforeTearDown(self):
self.abort()
self.portal.portal_skins.custom.manage_delObjects([self.id()])
self.tic()
def test_manage_addPythonScriptThroughZMI(self):
resp = self.publish(
'/{}/portal_skins/manage_addProduct/ERP5/addPythonScriptThroughZMIForm'.format(self.portal.getId()),
basic='ERP5TypeTestCase:',
handle_errors=False,
)
self.assertIn('ERP5 Python Scripts', resp.getBody())
self.assertIn('addPythonScriptThroughZMI', resp.getBody())
def test_call(self):
self.script.setBody('return "Hello"')
self.assertEqual(self.script(), "Hello")
self.script.setParameterSignature('who')
self.script.setBody('return "Hello " + who')
self.assertEqual(self.script("world"), "Hello world")
try:
self.script(123)
except TypeError:
_, _, tb = sys.exc_info()
# python script code is visible in traceback
self.assertEqual(
traceback.format_tb(tb)[-1],
' File "ERP5 Python Script", line 1, in %s\n'
' return "Hello " + who\n' % self.id()
)
else:
self.fail('Exception not raised')
class TestERP5WorkflowScript(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return 'erp5_core',
def afterSetUp(self):
wf = self.portal.portal_workflow.newContent(
portal_type='Workflow',
id=self.id()
)
self.script = wf.newContent(
portal_type='Workflow Script',
reference='test_script',
)
self.commit()
def beforeTearDown(self):
self.abort()
self.portal.portal_workflow.manage_delObjects([self.id()])
self.tic()
def test_default_params(self):
self.assertEqual(self.script.getParameterSignature(), 'state_change')
def test_call(self):
self.script.setBody('return "Hello " + state_change')
self.assertEqual(self.script("world"), "Hello world")
try:
self.script(123)
except TypeError:
_, _, tb = sys.exc_info()
# python script code is visible in traceback
self.assertEqual(
traceback.format_tb(tb)[-1],
' File "ERP5 Workflow Script", line 1, in script_test_script\n'
' return "Hello " + state_change\n'
)
else:
self.fail('Exception not raised')
...@@ -72,21 +72,19 @@ class TestSecurityMixin(ERP5TypeTestCase): ...@@ -72,21 +72,19 @@ class TestSecurityMixin(ERP5TypeTestCase):
i.e. those who have a docstring but have no security declaration. i.e. those who have a docstring but have no security declaration.
""" """
self._prepareDocumentList() self._prepareDocumentList()
white_method_id_list = ['om_icons',] allowed_method_id_list = ['om_icons',]
app = self.portal.aq_parent app = self.portal.aq_parent
meta_type_dict = {} meta_type_set = set([None])
error_dict = {} error_set = set()
for idx, obj in app.ZopeFind(app, search_sub=1): for _, obj in app.ZopeFind(app, search_sub=1):
meta_type = getattr(obj, 'meta_type', None) meta_type = getattr(obj, 'meta_type', None)
if meta_type is None: if meta_type in meta_type_set:
continue continue
if meta_type in meta_type_dict: meta_type_set.add(meta_type)
continue
meta_type_dict[meta_type] = True
if '__roles__' in obj.__class__.__dict__: if '__roles__' in obj.__class__.__dict__:
continue continue
for method_id in dir(obj): for method_id in dir(obj):
if method_id.startswith('_') or method_id in white_method_id_list or not callable(getattr(obj, method_id, None)): if method_id.startswith('_') or method_id in allowed_method_id_list or not callable(getattr(obj, method_id, None)):
continue continue
method = getattr(obj, method_id) method = getattr(obj, method_id)
if isinstance(method, MethodType) and \ if isinstance(method, MethodType) and \
...@@ -96,16 +94,19 @@ class TestSecurityMixin(ERP5TypeTestCase): ...@@ -96,16 +94,19 @@ class TestSecurityMixin(ERP5TypeTestCase):
method.__module__: method.__module__:
if method.__module__ == 'Products.ERP5Type.Accessor.WorkflowState' and method.func_code.co_name == 'serialize': if method.__module__ == 'Products.ERP5Type.Accessor.WorkflowState' and method.func_code.co_name == 'serialize':
continue continue
func_code = method.func_code func_code = method.__code__
error_dict[(func_code.co_filename, func_code.co_firstlineno, method_id)] = True error_set.add((func_code.co_filename, func_code.co_firstlineno, method_id))
error_list = error_dict.keys()
if os.environ.get('erp5_debug_mode', None): error_list = []
pass for filename, lineno, method_id in sorted(error_set):
else: # ignore security problems with non ERP5 documents, unless running in debug mode.
error_list = filter(lambda x:'/erp5/' in x[0], error_list) if os.environ.get('erp5_debug_mode') or '/erp5/' in filename or '<portal_components' in filename:
error_list.append('%s:%s %s' % (filename, lineno, method_id))
else:
print('Ignoring missing security definition for %s in %s:%s ' % (method_id, filename, lineno))
if error_list: if error_list:
message = '\nThe following %s methods have a docstring but have no security assertions.\n\t%s' \ message = '\nThe following %s methods have a docstring but have no security assertions.\n\t%s' \
% (len(error_list), '\n\t'.join(['%s:%s %s' % x for x in sorted(error_list)])) % (len(error_list), '\n\t'.join(error_list))
self.fail(message) self.fail(message)
def test_workflow_transition_protection(self): def test_workflow_transition_protection(self):
......
...@@ -158,7 +158,7 @@ def patch_linecache(): ...@@ -158,7 +158,7 @@ def patch_linecache():
return data.splitlines(True) if data is not None else () return data.splitlines(True) if data is not None else ()
if basename(filename) == 'Script (Python)': if basename(filename) in ('Script (Python)', 'ERP5 Python Script', 'ERP5 Workflow Script'):
try: try:
script = module_globals['script'] script = module_globals['script']
if script._p_jar.opened: if script._p_jar.opened:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment