Commit b9c41350 authored by Julien Muchembled's avatar Julien Muchembled

Review guards on PythonScript/ExternalMethod and expose a method to check them

- Code refactoring and small optimizations.
- Really fix unwanted acquisition.
- ExternalMethod: fix security declarations.
- Fix role/permission checking for ExternalMethod called by a PythonScript
  with proxy roles.
- When editing an existing guard, modify it instead of always recreate one.
  no very useful here, but that's good practice: this is nicer for the ZODB
  and it's easier to browse the history. BT should do the same when upgrading.
parent ec1a895c
...@@ -13,13 +13,8 @@ ...@@ -13,13 +13,8 @@
from inspect import getargs from inspect import getargs
from Products.ExternalMethod.ExternalMethod import * from Products.ExternalMethod.ExternalMethod import *
from AccessControl import ModuleSecurityInfo
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from Acquisition import aq_parent from .PythonScript import addGuard
from Products.ERP5Type.patches.PythonScript import _guard_form, \
_guard_manage_options, checkGuard, getGuard, manage_guardForm, \
manage_setGuard
from zExceptions import Forbidden
if 1: if 1:
def getFunction(self, reload=False, f=None): def getFunction(self, reload=False, f=None):
...@@ -89,10 +84,7 @@ if 1: ...@@ -89,10 +84,7 @@ if 1:
- fix magic "self" argument when positional arguments get their values - fix magic "self" argument when positional arguments get their values
from kw. from kw.
""" """
guard = getattr(self, 'guard', None) self.checkGuard(True)
if guard is not None:
if not checkGuard(guard, aq_parent(self)):
raise Forbidden, 'Calling %s %s is denied by Guard.' % (self.meta_type, self.id)
import erp5.component.extension import erp5.component.extension
component_module = erp5.component.extension.find_load_module(self._module) component_module = erp5.component.extension.find_load_module(self._module)
...@@ -147,17 +139,8 @@ if 1: ...@@ -147,17 +139,8 @@ if 1:
ExternalMethod.__call__ = __call__ ExternalMethod.__call__ = __call__
security = ModuleSecurityInfo('Products.ExternalMethod.ExternalMethod.ExternalMethod') ExternalMethod.security = ClassSecurityInfo()
ExternalMethod.manage_options += _guard_manage_options addGuard(ExternalMethod, change_external_methods)
ExternalMethod._guard_form = _guard_form
ExternalMethod.manage_guardForm = manage_guardForm InitializeClass(ExternalMethod)
security.declareProtected(view_management_screens, 'manage_guardForm')
ExternalMethod.getGuard = getGuard
ExternalMethod.manage_setGuard = manage_setGuard
security.declareProtected(change_external_methods, 'manage_setGuard')
InitializeClass(ExternalMethod)
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
from Products.DCWorkflow.Guard import Guard from Products.DCWorkflow.Guard import Guard
from Products.PythonScripts.PythonScript import PythonScript from Products.PythonScripts.PythonScript import PythonScript
from App.special_dtml import DTMLFile from App.special_dtml import DTMLFile
from Products.ERP5Type import _dtmldir from .. import _dtmldir
from AccessControl import ClassSecurityInfo, getSecurityManager from AccessControl import ClassSecurityInfo, getSecurityManager
from AccessControl.class_init import InitializeClass from AccessControl.class_init import InitializeClass
from AccessControl.PermissionRole import rolesForPermissionOn from AccessControl.PermissionRole import rolesForPermissionOn
...@@ -62,17 +62,17 @@ PythonScript.manage_main = manage_editForm ...@@ -62,17 +62,17 @@ PythonScript.manage_main = manage_editForm
PythonScript.manage_editDocument = manage_editForm PythonScript.manage_editDocument = manage_editForm
PythonScript.manage_editForm = manage_editForm PythonScript.manage_editForm = manage_editForm
### Guards
_guard_manage_options = ( _guard_manage_options = (
{ {
'label':'Guard', 'label':'Guard',
'action':'manage_guardForm', 'action':'manage_guardForm',
}, },
) )
PythonScript.manage_options += _guard_manage_options
_guard_form = DTMLFile( _guard_form = DTMLFile(
'editGuardForm', _dtmldir) 'editGuardForm', _dtmldir)
PythonScript._guard_form = _guard_form
def manage_guardForm(self, REQUEST, manage_tabs_message=None): def manage_guardForm(self, REQUEST, manage_tabs_message=None):
''' '''
...@@ -81,64 +81,63 @@ def manage_guardForm(self, REQUEST, manage_tabs_message=None): ...@@ -81,64 +81,63 @@ def manage_guardForm(self, REQUEST, manage_tabs_message=None):
management_view='Guard', management_view='Guard',
manage_tabs_message=manage_tabs_message, manage_tabs_message=manage_tabs_message,
) )
PythonScript.manage_guardForm = manage_guardForm
security.declareProtected('View management screens', 'manage_guardForm')
def manage_setGuard(self, props=None, REQUEST=None): def manage_setGuard(self, props=None, REQUEST=None):
''' '''
''' '''
g = Guard() g = Guard()
if g.changeFromProperties(props or REQUEST): if g.changeFromProperties(props or REQUEST):
self.guard = g guard = self.guard
if guard is None:
self.guard = g
else:
guard._p_activate()
if guard.__dict__ != g.__dict__:
guard.__dict__.clear()
guard.__dict__.update(g.__dict__)
guard._p_changed = 1
else: else:
self.guard = None try:
del self.guard
except AttributeError:
pass
if REQUEST is not None: if REQUEST is not None:
return self.manage_guardForm(REQUEST, 'Properties changed.') return self.manage_guardForm(REQUEST, 'Properties changed.')
PythonScript.manage_setGuard = manage_setGuard
security.declareProtected('Change Python Scripts', 'manage_setGuard')
def getGuard(self): def getGuard(self):
guard = getattr(self, 'guard', None) guard = self.guard
if guard is not None: if guard is None:
return guard
else:
return Guard().__of__(self) # Create a temporary guard. return Guard().__of__(self) # Create a temporary guard.
PythonScript.getGuard = getGuard return guard
def checkGuard(guard, ob): def getRoles(ob):
sm = getSecurityManager()
stack = sm._context.stack
if stack:
proxy_roles = getattr(stack[-1], '_proxy_roles', None)
if proxy_roles:
return set(proxy_roles)
return set(sm.getUser().getRolesInContext(ob))
def _checkGuard(guard, ob):
# returns 1 if guard passes against ob, else 0. # returns 1 if guard passes against ob, else 0.
# TODO : implement TALES evaluation by defining an appropriate # TODO : implement TALES evaluation by defining an appropriate
# context. # context.
u_roles = None
def getRoles():
sm = getSecurityManager()
u = sm.getUser()
stack = sm._context.stack
if stack and len(stack) > 1:
eo = stack[-2] # -1 is the current script.
proxy_roles = getattr(eo, '_proxy_roles', None)
if proxy_roles:
roles = proxy_roles
return proxy_roles
roles = u.getRolesInContext(ob)
return roles
if guard.permissions: if guard.permissions:
# Require at least one role for required roles for the given permission. # Require at least one role for required roles for the given permission.
if u_roles is None: u_roles = getRoles(ob)
u_roles = getRoles()
for p in guard.permissions: for p in guard.permissions:
if set(rolesForPermissionOn(p, ob)).intersection(u_roles): if not u_roles.isdisjoint(rolesForPermissionOn(p, ob)):
break break
else: else:
return 0 return 0
else:
u_roles = None
if guard.roles: if guard.roles:
# Require at least one of the given roles. # Require at least one of the given roles.
if u_roles is None: if u_roles is None:
u_roles = getRoles() u_roles = getRoles(ob)
for role in guard.roles: if u_roles.isdisjoint(guard.roles):
if role in u_roles:
break
else:
return 0 return 0
if guard.groups: if guard.groups:
# Require at least one of the specified groups. # Require at least one of the specified groups.
...@@ -158,15 +157,42 @@ def checkGuard(guard, ob): ...@@ -158,15 +157,42 @@ def checkGuard(guard, ob):
return 0 return 0
return 1 return 1
PythonScript_exec = PythonScript._exec def checkGuard(aq_parent=aq_parent, _checkGuard=_checkGuard):
def _exec(self, *args): def checkGuard(self, _exec=False):
# PATCH BEGIN : check guard against context, if guard exists. guard = self.guard
guard = getattr(aq_base(self), 'guard', None) if guard is None or _checkGuard(guard, aq_parent(self)):
if guard is not None: return 1
if not checkGuard(guard, aq_parent(self)): if _exec:
raise Forbidden, 'Calling %s %s is denied by Guard.' % (self.meta_type, self.id) raise Forbidden('Calling %s %s is denied by Guard.'
# PATCH END % (self.meta_type, self.id))
return PythonScript_exec(self, *args) return checkGuard
PythonScript._exec = _exec checkGuard = checkGuard()
def addGuard(cls, set_permission):
security = cls.security
cls.guard = None
cls.getGuard = getGuard
cls.checkGuard = checkGuard
cls.manage_options += _guard_manage_options
cls._guard_form = _guard_form
security.declareProtected('View management screens', 'manage_guardForm')
cls.manage_guardForm = manage_guardForm
security.declareProtected(set_permission, 'manage_setGuard')
cls.manage_setGuard = manage_setGuard
addGuard(PythonScript, 'Change Python Scripts')
def __call__(self, *args, **kw):
'''Calls the script.'''
self.checkGuard(True) # patch
return self._bindAndExec(args, kw, None)
security.declarePublic("render")
PythonScript.__call__ = PythonScript.render = __call__
InitializeClass(PythonScript) InitializeClass(PythonScript)
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