Commit 63dcec59 authored by Jérome Perrin's avatar Jérome Perrin

Security of tester accessors

The first commit of this merge request was introduced for zope4py3 branch but it revealed issues on py2 as well, i.e. we don't have roles in auto-generated tester methods.

```
======================================================================
FAIL: test_method_protection (testSecurity.TestSecurity)
This test will list all implicitly Public methods in any objects in ZODB.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/(SR)/parts/erp5/Products/ERP5/tests/testSecurity.py", line 113, in test_method_protection
    self.fail(message)
AssertionError: 
The following 7 methods have a docstring but have no security assertions.
	/(SR)/parts/erp5/product/ERP5Form/PreferenceTool.py:65 isPreferredVcsPushMode
	/srv/slapgrid/slappart19/t/eiy/soft/8a7759fd7b65b20d9f87713605745d05/parts/erp5/product/ERP5Type/Accessor/AcquiredProperty.py:217 hasTelephoneValidationState
	/(SR)/parts/erp5/product/ERP5Type/Accessor/Base.py:219 hasViewFormIdList
	/(SR)/parts/erp5/product/ERP5Type/Accessor/Constant.py:94 isWebDocumentType
	/(SR)/parts/erp5/product/ERP5Type/Accessor/Content.py:224 hasImage
	/(SR)/parts/erp5/product/ERP5Type/Accessor/ContentProperty.py:283 hasImageWidthList
	/(SR)/parts/erp5/product/ERP5Type/Accessor/Translation.py:233 hasFrTranslatedTitle

----------------------------------------------------------------------
```

See merge request !1911
parents 2ca25775 3992822a
......@@ -67,10 +67,10 @@ class TestRenderViewAPI(ERP5TypeTestCase):
def test_signature(self):
for field in FieldRegistry.get_field_classes().itervalues(): # pylint: disable=no-value-for-parameter
self.assertEqual(('self', 'value', 'REQUEST', 'render_prefix'),
field.render_view.__func__.func_code.co_varnames)
field.render_view.__code__.co_varnames)
if field is not ProxyField:
self.assertEqual(('self', 'field', 'value', 'REQUEST'),
field.widget.render_view.__func__.func_code.co_varnames[:4], '%s %s' % (field.widget, field.widget.render_view.__func__.func_code.co_varnames[:4]))
field.widget.render_view.__code__.co_varnames[:4], '%s %s' % (field.widget, field.widget.render_view.__code__.co_varnames[:4]))
class TestFloatField(ERP5TypeTestCase):
......
......@@ -126,8 +126,8 @@ class TestFacebookLogin(ERP5TypeTestCase):
'erp5.component.extension.FacebookLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_callbackFacebookLogin(code=CODE)
getAccessTokenFromCode_mock.assert_called_once()
getUserEntry_mock.assert_called_once()
......@@ -166,8 +166,8 @@ class TestFacebookLogin(ERP5TypeTestCase):
'erp5.component.extension.FacebookLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_callbackFacebookLogin(code=CODE)
ac_cookie, = [v for (k, v) in response.listHeaders() if k.lower() == 'set-cookie' and '__ac_facebook_hash=' in v]
......@@ -232,8 +232,8 @@ return credential_request
'erp5.component.extension.FacebookLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
response = self.portal.ERP5Site_callbackFacebookLogin(code=CODE)
facebook_hash = self.portal.REQUEST.RESPONSE.cookies.get("__ac_facebook_hash")["value"]
self.assertEqual("8cec04e21e927f1023f4f4980ec11a77", facebook_hash)
......
......@@ -163,8 +163,8 @@ class TestGoogleLogin(GoogleLoginTestCase):
'erp5.component.extension.GoogleLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_receiveGoogleCallback(code=CODE)
getAccessTokenFromCode_mock.assert_called_once()
getUserEntry_mock.assert_called_once()
......@@ -207,8 +207,8 @@ class TestGoogleLogin(GoogleLoginTestCase):
'erp5.component.extension.GoogleLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_receiveGoogleCallback(code=CODE)
getAccessTokenFromCode_mock.assert_called_once()
......@@ -278,8 +278,8 @@ return credential_request
'erp5.component.extension.GoogleLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
response = self.portal.ERP5Site_receiveGoogleCallback(code=CODE)
getAccessTokenFromCode_mock.assert_called_once()
getUserEntry_mock.assert_called_once()
......
......@@ -126,8 +126,8 @@ class TestOpenIdConnectLogin(OpenIdConnectLoginTestCase):
'erp5.component.extension.OpenIdConnectLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_receiveOpenIdCallback(code=CODE, state=state)
getAccessTokenFromCode_mock.assert_called_once()
......@@ -165,8 +165,8 @@ class TestOpenIdConnectLogin(OpenIdConnectLoginTestCase):
'erp5.component.extension.OpenIdConnectLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_receiveOpenIdCallback(code=CODE, state=state)
getAccessTokenFromCode_mock.assert_called_once()
getUserEntry_mock.assert_called_once()
......@@ -260,8 +260,8 @@ return credential_request
'erp5.component.extension.OpenIdConnectLoginUtility.getUserEntry',
side_effect=getUserEntry
) as getUserEntry_mock:
getAccessTokenFromCode_mock.func_code = getAccessTokenFromCode.func_code
getUserEntry_mock.func_code = getUserEntry.func_code
getAccessTokenFromCode_mock.__code__ = getAccessTokenFromCode.__code__
getUserEntry_mock.__code__ = getUserEntry.__code__
self.portal.ERP5Site_receiveOpenIdCallback(code=CODE, state=state)
getAccessTokenFromCode_mock.assert_called_once()
getUserEntry_mock.assert_called_once()
......
......@@ -75,7 +75,7 @@ class TestSecurityMixin(ERP5TypeTestCase):
allowed_method_id_list = ['om_icons',]
app = self.portal.aq_parent
meta_type_set = set([None])
error_set = set()
error_dict = {}
for _, obj in app.ZopeFind(app, search_sub=1):
meta_type = getattr(obj, 'meta_type', None)
if meta_type in meta_type_set:
......@@ -88,17 +88,20 @@ class TestSecurityMixin(ERP5TypeTestCase):
continue
method = getattr(obj, method_id)
if isinstance(method, MethodType) and \
getattr(method, 'func_name', None) is not None and \
getattr(method, '__name__', None) is not None and \
method.__doc__ and \
not hasattr(obj, '%s__roles__' % method_id) and \
not hasattr(method, '__roles__') and \
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.__code__.co_name == 'serialize':
continue
func_code = method.__code__
error_set.add((func_code.co_filename, func_code.co_firstlineno, method_id))
if not hasattr(func_code, 'co_filename'): # ERP5 Accessor
func_code = method.__func__.__class__.__init__.__code__
error_dict[(func_code.co_filename, func_code.co_firstlineno)] = method_id
error_list = []
for filename, lineno, method_id in sorted(error_set):
for (filename, lineno), method_id in sorted(error_dict.items()):
# ignore security problems with non ERP5 documents, unless running in debug mode.
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))
......
......@@ -31,7 +31,7 @@ from AccessControl import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager,\
setSecurityManager, newSecurityManager
from AccessControl.ZopeGuards import guarded_getattr
from MethodObject import Method
from Products.ERP5Type.Accessor.Base import Getter
from Products.ERP5Type.Globals import InitializeClass, DTMLFile
from zLOG import LOG, PROBLEM
......@@ -54,7 +54,7 @@ class Priority:
class func_code: pass
class PreferenceMethod(Method):
class PreferenceMethod(Getter):
""" A method object that lookup the attribute on preferences. """
# This is required to call the method form the Web
__code__ = func_code = func_code()
......
......@@ -82,7 +82,7 @@ class Dummy(Reindex):
self._id = id
self.__name__ = id
self._accessor_id = accessor_id
# self.__code__ = func_code = getattr(instance, self._accessor_id).func_code
# self.__code__ = func_code = getattr(instance, self._accessor_id).__code__
def __call__(self, instance, *args, **kw):
method = getattr(instance, self._accessor_id)
......
......@@ -203,19 +203,10 @@ class Getter(Method):
return getattr(roles, '__of__', lambda aq_parent: roles)(self)
class Tester(Method):
class Tester(Getter):
"""
Tests if an attribute value exists
"""
_need__name__=1
# Generic Definition of Method Object
# This is required to call the method form the Web
__code__ = func_code = func_code()
__code__.co_varnames = ('self',)
__code__.co_argcount = 1
__defaults__ = func_defaults = ()
def __init__(self, id, key, property_type, storage_id=None):
self._id = id
self.__name__ = id
......
......@@ -96,6 +96,8 @@ class Getter(Accessor):
self._key = key
self.__name__ = id
self.value = value
# Do not publish deprecated Constant getter
self.__doc__ = None
def __call__(self, instance):
return self.value
......@@ -208,7 +208,7 @@ class ListGetter(Base.Getter):
DefaultGetter = Getter
class Tester(Method):
class Tester(Base.Tester):
"""
Tests if an attribute value exists
"""
......
......@@ -267,7 +267,7 @@ class ListGetter(Base.Getter):
DefaultGetter = Getter
class Tester(Method):
class Tester(Base.Tester):
"""
Tests if an attribute value exists
"""
......
......@@ -31,7 +31,7 @@ from zLOG import LOG
from Products.ERP5Type.PsycoWrapper import psyco
from Acquisition import aq_base
from Products.ERP5Type.Accessor.Base import func_code, ATTRIBUTE_PREFIX, evaluateTales, Getter as BaseGetter, Method
from Products.ERP5Type.Accessor.Base import func_code, ATTRIBUTE_PREFIX, evaluateTales, Getter as BaseGetter, Setter as BaseSetter, Tester as BaseTester
from Products.ERP5Type.Accessor import Accessor, AcquiredProperty
from Products.ERP5Type.Accessor.TypeDefinition import type_definition
from Products.ERP5Type.Utils import unicode2str
......@@ -218,7 +218,7 @@ class AcquiredPropertyGetter(AcquiredProperty.Getter):
else:
return default
class TranslatedPropertyTester(Method):
class TranslatedPropertyTester(BaseTester):
"""
Tests if an attribute value exists
"""
......
......@@ -353,7 +353,7 @@ def transactional_cached(key_method=_default_key_method):
def decorator(function):
# Unfornately, we can only check functions (not other callable like class).
assert (key_method is not _default_key_method or
not getattr(function, 'func_defaults', None)), (
not getattr(function, '__defaults__', None)), (
"default 'key_method' of 'transactional_cached' does not work with"
" functions having default values for parameters")
key = repr(function)
......
......@@ -83,14 +83,18 @@ class _(PatchClass(ExternalMethod)):
return _f
except AttributeError:
pass
code = f.func_code
code = f.__code__
argument_object = getargs(code)
# reconstruct back the original names
arg_list = argument_object.args[:]
if argument_object.varargs:
arg_list.append('*' + argument_object.varargs)
if argument_object.keywords:
arg_list.append('**' + argument_object.keywords)
if six.PY2:
if argument_object.keywords:
arg_list.append('**' + argument_object.keywords)
else:
if argument_object.varkw:
arg_list.append('**' + argument_object.varkw)
i = isinstance(f, MethodType)
ff = six.get_unbound_function(f) if i else f
......@@ -98,7 +102,10 @@ class _(PatchClass(ExternalMethod)):
i += has_self
if i:
code = FuncCode(ff, i)
self._v_f = _f = (f, f.func_defaults, code, has_self, arg_list)
try: # This fails with mock function
self._v_f = _f = (f, f.__defaults__, code, has_self, arg_list)
except AttributeError:
self._v_f = _f = (f, f.func_defaults, code, has_self, arg_list)
return _f
def __call__(self, *args, **kw):
......
......@@ -59,4 +59,4 @@ try:
except TypeError:
# We need to monkey patch in-place, as it is a top-level function and
# already imported in other places.
convertToUnicode.func_code = patched_convertToUnicode.func_code
convertToUnicode.__code__ = patched_convertToUnicode.__code__
......@@ -57,7 +57,7 @@ def special_extract_tb(tb, limit = None):
else: line = None
# display where we failed in the sequence
if co == Sequence.play.func_code:
if co == Sequence.play.__code__:
if line is None:
line = ''
sequence = f.f_locals['self']
......
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