Commit fcf1d15c authored by Arnaud Fontaine's avatar Arnaud Fontaine

Add Developer Role to modify ZODB Components.

This new Role is defined only on portal_components and users can be only added
to this Role through editing zope.conf. Also, add a Permission for reset as
this role is not available outside of portal_components and is still useful
for Workflows for example.
parent 279e67a9
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</item> </item>
<item> <item>
<key> <string>acquire_local_roles</string> </key> <key> <string>acquire_local_roles</string> </key>
<value> <int>1</int> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>content_icon</string> </key> <key> <string>content_icon</string> </key>
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</item> </item>
<item> <item>
<key> <string>acquire_local_roles</string> </key> <key> <string>acquire_local_roles</string> </key>
<value> <int>0</int> </value> <value> <int>1</int> </value>
</item> </item>
<item> <item>
<key> <string>content_icon</string> </key> <key> <string>content_icon</string> </key>
......
...@@ -43,12 +43,7 @@ ...@@ -43,12 +43,7 @@
<item> <item>
<key> <string>permissions</string> </key> <key> <string>permissions</string> </key>
<value> <value>
<tuple> <tuple/>
<string>Access contents information</string>
<string>Modify portal content</string>
<string>View</string>
<string>Add portal content</string>
</tuple>
</value> </value>
</item> </item>
<item> <item>
......
2012-02-28 arnaud.fontaine
* Add Developer Role on portal_components which is the only Role with write permissions on portal_components and subobjects.
* Modify component_validation_workflow to not set Permissions and let it get it from portal_components instead.
2012-02-25 arnaud.fontaine 2012-02-25 arnaud.fontaine
* Add a Python script which performs the redirect rather than doing it in BusinessTemplate class. * Add a Python script which performs the redirect rather than doing it in BusinessTemplate class.
* Rename *MigrateAllComponentFromFilesystem to *MigrateSourceCodeFromFilesystem. * Rename *MigrateAllComponentFromFilesystem to *MigrateSourceCodeFromFilesystem.
......
41011 41012
\ No newline at end of file \ No newline at end of file
...@@ -19,7 +19,7 @@ from Products.ERP5Type.Globals import InitializeClass ...@@ -19,7 +19,7 @@ from Products.ERP5Type.Globals import InitializeClass
from Acquisition import aq_inner, aq_parent from Acquisition import aq_inner, aq_parent
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from App.config import getConfiguration
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.interfaces.plugins import IUserFactoryPlugin from Products.PluggableAuthService.interfaces.plugins import IUserFactoryPlugin
...@@ -92,6 +92,8 @@ class ERP5User(PropertiedUser): ...@@ -92,6 +92,8 @@ class ERP5User(PropertiedUser):
continue continue
break break
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
return list( self.getRoles() ) + local.keys() return list( self.getRoles() ) + local.keys()
def allowed( self, object, object_roles=None ): def allowed( self, object, object_roles=None ):
...@@ -106,6 +108,17 @@ class ERP5User(PropertiedUser): ...@@ -106,6 +108,17 @@ class ERP5User(PropertiedUser):
if object_roles is None or 'Anonymous' in object_roles: if object_roles is None or 'Anonymous' in object_roles:
return 1 return 1
# Check for Developer Role, see patches.User for rationale
# XXX-arnau: copy/paste
object_roles = set(object_roles)
if 'Developer' in object_roles:
object_roles.remove('Developer')
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5', None)
if config and self.getId() in config.developer_list:
return 1
# Provide short-cut access if object is protected by 'Authenticated' # Provide short-cut access if object is protected by 'Authenticated'
# role and user is not nobody # role and user is not nobody
if 'Authenticated' in object_roles and ( if 'Authenticated' in object_roles and (
......
...@@ -146,3 +146,4 @@ AddERP5Content = AddPortalContent # Since we put come CPS content in ERP5 docume ...@@ -146,3 +146,4 @@ AddERP5Content = AddPortalContent # Since we put come CPS content in ERP5 docume
# Source Code Management - this is the highest possible permission # Source Code Management - this is the highest possible permission
ManageExtensions = "Manage extensions" ManageExtensions = "Manage extensions"
ResetDynamicClasses = "Reset dynamic classes"
...@@ -71,7 +71,7 @@ class ComponentTool(BaseTool): ...@@ -71,7 +71,7 @@ class ComponentTool(BaseTool):
del sys.modules[full_module_name] del sys.modules[full_module_name]
delattr(module, name) delattr(module, name)
security.declareProtected(Permissions.ModifyPortalContent, 'reset') security.declareProtected(Permissions.ResetDynamicClasses, 'reset')
def reset(self, force=True): def reset(self, force=True):
""" """
XXX-arnau: global reset XXX-arnau: global reset
...@@ -117,7 +117,7 @@ class ComponentTool(BaseTool): ...@@ -117,7 +117,7 @@ class ComponentTool(BaseTool):
type_tool.resetDynamicDocumentsOnceAtTransactionBoundary() type_tool.resetDynamicDocumentsOnceAtTransactionBoundary()
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ResetDynamicClasses,
'resetOnceAtTransactionBoundary') 'resetOnceAtTransactionBoundary')
def resetOnceAtTransactionBoundary(self): def resetOnceAtTransactionBoundary(self):
""" """
......
...@@ -70,6 +70,7 @@ from Products.ERP5Type.patches import ZopePageTemplateUtils ...@@ -70,6 +70,7 @@ from Products.ERP5Type.patches import ZopePageTemplateUtils
from Products.ERP5Type.patches import OFSHistory from Products.ERP5Type.patches import OFSHistory
from Products.ERP5Type.patches import OFSItem from Products.ERP5Type.patches import OFSItem
from Products.ERP5Type.patches import ExternalMethod from Products.ERP5Type.patches import ExternalMethod
from Products.ERP5Type.patches import User
# These symbols are required for backward compatibility # These symbols are required for backward compatibility
from Products.ERP5Type.patches.PropertyManager import ERP5PropertyManager from Products.ERP5Type.patches.PropertyManager import ERP5PropertyManager
......
<component>
<sectiontype name="ERP5Type"
implements="zope.product.base">
<description>
Description
</description>
<key name="developers"
attribute="developer_list"
datatype="string-list">
<description>
Description
</description>
</key>
</sectiontype>
</component>
...@@ -83,10 +83,14 @@ def getRolesInContext( self, object ): ...@@ -83,10 +83,14 @@ def getRolesInContext( self, object ):
continue continue
break break
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
return list( self.getRoles() ) + local.keys() return list( self.getRoles() ) + local.keys()
def allowed( self, object, object_roles=None ): from App.config import getConfiguration
def allowed(self, object, object_roles=None ):
""" Check whether the user has access to object. """ Check whether the user has access to object.
...@@ -105,6 +109,17 @@ def allowed( self, object, object_roles=None ): ...@@ -105,6 +109,17 @@ def allowed( self, object, object_roles=None ):
if object_roles is None or 'Anonymous' in object_roles: if object_roles is None or 'Anonymous' in object_roles:
return 1 return 1
# Check for Developer Role, see patches.User for rationale
# XXX-arnau: copy/paste
object_roles = set(object_roles)
if 'Developer' in object_roles:
object_roles.remove('Developer')
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5', None)
if config and self.getId() in config.developer_list:
return 1
# Provide short-cut access if object is protected by 'Authenticated' # Provide short-cut access if object is protected by 'Authenticated'
# role and user is not nobody # role and user is not nobody
if 'Authenticated' in object_roles and ( if 'Authenticated' in object_roles and (
......
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
# Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this
# distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from AccessControl.User import BasicUser
BasicUser_allowed = BasicUser.allowed
def allowed(self, object, object_roles=None):
"""
Check if the user has Developer role which allows to modify ZODB source code
and remove it, as it should never be acquired anyhow, before calling the
original method
"""
# XXX-arnau: copy/paste (PropertiedUser)
if object_roles is not None:
object_roles = set(object_roles)
if 'Developer' in object_roles:
object_roles.remove('Developer')
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5', None)
if config and self.getId() in config.developer_list:
return 1
return BasicUser_allowed(self, object, object_roles)
BasicUser.allowed = allowed
from App.config import getConfiguration
from AccessControl.User import SimpleUser
SimpleUser_getRoles = SimpleUser.getRoles
def getRoles(self):
"""
Add Developer Role if the user has been explicitely set as Developer in Zope
configuration file
"""
role_tuple = SimpleUser_getRoles(self)
if role_tuple:
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5', None)
if config:
role_set = set(role_tuple)
user_id = self.getId()
if config and user_id in config.developer_list:
role_set.add('Developer')
elif user_id in role_set:
role_set.remove('Developer')
return role_set
return role_tuple
SimpleUser.getRoles = getRoles
SimpleUser_getRolesInContext = SimpleUser.getRolesInContext
def getRolesInContext(self, object):
"""
Return the list of roles assigned to the user, including local roles
assigned in context of the passed in object.
"""
userid=self.getId()
roles=self.getRoles()
local={}
object=getattr(object, 'aq_inner', object)
while 1:
local_roles = getattr(object, '__ac_local_roles__', None)
if local_roles:
if callable(local_roles):
local_roles=local_roles()
dict=local_roles or {}
for r in dict.get(userid, []):
local[r]=1
inner = getattr(object, 'aq_inner', object)
parent = getattr(inner, '__parent__', None)
if parent is not None:
object = parent
continue
if hasattr(object, 'im_self'):
object=object.im_self
object=getattr(object, 'aq_inner', object)
continue
break
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
roles=list(roles) + local.keys()
return roles
SimpleUser.getRolesInContext = getRolesInContext
...@@ -1219,8 +1219,10 @@ def assertResetCalled(self, *args, **kwargs): ...@@ -1219,8 +1219,10 @@ def assertResetCalled(self, *args, **kwargs):
import abc import abc
from Products.ERP5Type.mixin.component import ComponentMixin from Products.ERP5Type.mixin.component import ComponentMixin
from Products.ERP5Type.tests.SecurityTestCase import SecurityTestCase
from App.config import getConfiguration
class _TestZodbComponent(ERP5TypeTestCase): class _TestZodbComponent(SecurityTestCase):
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
...@@ -1228,6 +1230,19 @@ class _TestZodbComponent(ERP5TypeTestCase): ...@@ -1228,6 +1230,19 @@ class _TestZodbComponent(ERP5TypeTestCase):
'erp5_core_component') 'erp5_core_component')
def afterSetUp(self): def afterSetUp(self):
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config is None:
class DummyDeveloperConfig(object):
pass
dummy_developer_config = DummyDeveloperConfig()
dummy_developer_config.developer_list = ['ERP5TypeTestCase']
getConfiguration().product_config = {'erp5': dummy_developer_config}
elif 'ERP5TypeTestCase' not in product_config['erp5'].developer_list:
product_config['erp5'].developer_list.append('ERP5TypeTestCase')
self._portal = self.getPortal() self._portal = self.getPortal()
self._component_tool = self._portal.portal_components self._component_tool = self._portal.portal_components
self._module = __import__(self._getComponentModuleName(), self._module = __import__(self._getComponentModuleName(),
...@@ -1592,6 +1607,49 @@ def bar(*args, **kwargs): ...@@ -1592,6 +1607,49 @@ def bar(*args, **kwargs):
transaction.commit() transaction.commit()
self.tic() self.tic()
def testDeveloperRoleSecurity(self):
"""
XXX-arnau: test with different users and workflows
"""
component = self._newComponent('TestDeveloperRoleSecurity',
'def foo():\n print "ok"')
transaction.commit()
self.tic()
user_id = 'ERP5TypeTestCase'
self.assertUserCanChangeLocalRoles(user_id, self._component_tool)
self.assertUserCanModifyDocument(user_id, self._component_tool)
self.assertUserCanDeleteDocument(user_id, self._component_tool)
self.assertUserCanChangeLocalRoles(user_id, component)
self.assertUserCanDeleteDocument(user_id, component)
getConfiguration().product_config['erp5'].developer_list = []
# Component Tool and the Component should be viewable by Manager
self.assertUserCanViewDocument(user_id, self._component_tool)
self.assertUserCanAccessDocument(user_id, self._component_tool)
self.assertUserCanViewDocument(user_id, component)
self.assertUserCanAccessDocument(user_id, component)
# But nothing else should be permitted on Component Tool nor Component
self.failIfUserCanAddDocument(user_id, self._component_tool)
self.failIfUserCanModifyDocument(user_id, self._component_tool)
self.failIfUserCanDeleteDocument(user_id, self._component_tool)
self.failIfUserCanModifyDocument(user_id, component)
self.failIfUserCanDeleteDocument(user_id, component)
self.failIfUserCanChangeLocalRoles(user_id, component)
getConfiguration().product_config['erp5'].developer_list = [user_id]
self.assertUserCanChangeLocalRoles(user_id, self._component_tool)
self.assertUserCanModifyDocument(user_id, self._component_tool)
self.assertUserCanDeleteDocument(user_id, self._component_tool)
self.assertUserCanChangeLocalRoles(user_id, component)
self.assertUserCanModifyDocument(user_id, component)
self.assertUserCanDeleteDocument(user_id, component)
from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent
class TestZodbExtensionComponent(_TestZodbComponent): class TestZodbExtensionComponent(_TestZodbComponent):
......
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