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 @@
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>1</int> </value>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>content_icon</string> </key>
......
......@@ -27,7 +27,7 @@
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>0</int> </value>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>content_icon</string> </key>
......
......@@ -43,12 +43,7 @@
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Access contents information</string>
<string>Modify portal content</string>
<string>View</string>
<string>Add portal content</string>
</tuple>
<tuple/>
</value>
</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
* Add a Python script which performs the redirect rather than doing it in BusinessTemplate class.
* Rename *MigrateAllComponentFromFilesystem to *MigrateSourceCodeFromFilesystem.
......
41011
\ No newline at end of file
41012
\ No newline at end of file
......@@ -19,7 +19,7 @@ from Products.ERP5Type.Globals import InitializeClass
from Acquisition import aq_inner, aq_parent
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from App.config import getConfiguration
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.interfaces.plugins import IUserFactoryPlugin
......@@ -92,6 +92,8 @@ class ERP5User(PropertiedUser):
continue
break
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
return list( self.getRoles() ) + local.keys()
def allowed( self, object, object_roles=None ):
......@@ -106,6 +108,17 @@ class ERP5User(PropertiedUser):
if object_roles is None or 'Anonymous' in object_roles:
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'
# role and user is not nobody
if 'Authenticated' in object_roles and (
......
......@@ -146,3 +146,4 @@ AddERP5Content = AddPortalContent # Since we put come CPS content in ERP5 docume
# Source Code Management - this is the highest possible permission
ManageExtensions = "Manage extensions"
ResetDynamicClasses = "Reset dynamic classes"
......@@ -71,7 +71,7 @@ class ComponentTool(BaseTool):
del sys.modules[full_module_name]
delattr(module, name)
security.declareProtected(Permissions.ModifyPortalContent, 'reset')
security.declareProtected(Permissions.ResetDynamicClasses, 'reset')
def reset(self, force=True):
"""
XXX-arnau: global reset
......@@ -117,7 +117,7 @@ class ComponentTool(BaseTool):
type_tool.resetDynamicDocumentsOnceAtTransactionBoundary()
security.declareProtected(Permissions.ModifyPortalContent,
security.declareProtected(Permissions.ResetDynamicClasses,
'resetOnceAtTransactionBoundary')
def resetOnceAtTransactionBoundary(self):
"""
......
......@@ -70,6 +70,7 @@ from Products.ERP5Type.patches import ZopePageTemplateUtils
from Products.ERP5Type.patches import OFSHistory
from Products.ERP5Type.patches import OFSItem
from Products.ERP5Type.patches import ExternalMethod
from Products.ERP5Type.patches import User
# These symbols are required for backward compatibility
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>
......@@ -84,9 +84,13 @@ def getRolesInContext( self, object ):
break
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
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.
......@@ -105,6 +109,17 @@ def allowed( self, object, object_roles=None ):
if object_roles is None or 'Anonymous' in object_roles:
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'
# role and user is not nobody
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):
import abc
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
def getBusinessTemplateList(self):
......@@ -1228,6 +1230,19 @@ class _TestZodbComponent(ERP5TypeTestCase):
'erp5_core_component')
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._component_tool = self._portal.portal_components
self._module = __import__(self._getComponentModuleName(),
......@@ -1592,6 +1607,49 @@ def bar(*args, **kwargs):
transaction.commit()
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
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