############################################################################## # # Copyright (c) 2002 Zope Corporation 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 # ############################################################################## __version__='$Revision$'[11:-2] from Globals import InitializeClass from AccessControl import ClassSecurityInfo from AccessControl import getSecurityManager from AccessControl.Permissions import view_management_screens from AccessControl.PermissionRole import _what_not_even_god_should_do from AccessControl.ZopeGuards import guarded_getattr from Persistence import Persistent from string import join, strip import re defaultBindings = {'name_context': 'context', 'name_container': 'container', 'name_m_self': 'script', 'name_ns': '', 'name_subpath': 'traverse_subpath'} _marker = [] # Create a new marker class NameAssignments: # Note that instances of this class are intended to be immutable # and persistent but not inherit from ExtensionClass. _exprs = (('name_context', 'self._getContext()'), ('name_container', 'self._getContainer()'), ('name_m_self', 'self'), ('name_ns', 'self._getNamespace(caller_namespace, kw)'), ('name_subpath', 'self._getTraverseSubpath()'), ) _isLegalName = re.compile(r'_$|[a-zA-Z][a-zA-Z0-9_]*$').match _asgns = {} __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, mapping): # mapping is presumably the REQUEST or compatible equivalent. # Note that we take care not to store expression texts in the ZODB. asgns = {} _isLegalName = self._isLegalName for name, expr in self._exprs: if mapping.has_key(name): assigned_name = strip(mapping[name]) if not assigned_name: continue if not _isLegalName(assigned_name): raise ValueError, ('"%s" is not a valid variable name.' % assigned_name) asgns[name] = assigned_name self._asgns = asgns def isAnyNameAssigned(self): if len(self._asgns) > 0: return 1 return 0 def isNameAssigned(self, name): return self._asgns.has_key(name) def getAssignedName(self, name, default=_marker): val = self._asgns.get(name, default) if val is _marker: raise KeyError, name return val def getAssignedNames(self): # Returns a copy of the assigned names mapping return self._asgns.copy() def getAssignedNamesInOrder(self): # Returns the assigned names in the same order as that of # self._exprs. rval = [] asgns = self._asgns for name, expr in self._exprs: if asgns.has_key(name): assigned_name = asgns[name] rval.append(assigned_name) return rval def _generateCodeBlock(self, bindtext, assigned_names): # Returns a tuple: exec-able code that can compute the value of # the bindings and eliminate clashing keyword arguments, # and the number of names bound. text = ['bound_data.append(%s)\n' % bindtext] for assigned_name in assigned_names: text.append('if kw.has_key("%s"):\n' % assigned_name) text.append(' del kw["%s"]\n' % assigned_name) codetext = join(text, '') return (compile(codetext, '', 'exec'), len(assigned_names)) def _createCodeBlockForMapping(self): # Generates a code block which generates the "bound_data" # variable and removes excessive arguments from the "kw" # variable. bound_data will be a mapping, for use as a # global namespace. exprtext = [] assigned_names = [] asgns = self._asgns for name, expr in self._exprs: if asgns.has_key(name): assigned_name = asgns[name] assigned_names.append(assigned_name) exprtext.append('"%s":%s,' % (assigned_name, expr)) text = '{%s}' % join(exprtext, '') return self._generateCodeBlock(text, assigned_names) def _createCodeBlockForTuple(self, argNames): # Generates a code block which generates the "bound_data" # variable and removes excessive arguments from the "kw" # variable. bound_data will be a tuple, for use as # positional arguments. assigned_names = [] exprtext = [] asgns = self._asgns for argName in argNames: passedLastBoundArg = 1 for name, expr in self._exprs: # Provide a value for the available exprs. if asgns.has_key(name): assigned_name = asgns[name] if assigned_name == argName: # The value for this argument will be filled in. exprtext.append('%s,' % expr) assigned_names.append(assigned_name) passedLastBoundArg = 0 break if passedLastBoundArg: # Found last of bound args. break text = '(%s)' % join(exprtext, '') return self._generateCodeBlock(text, assigned_names) from AccessControl.unauthorized import Unauthorized class UnauthorizedBinding: """Explanation: as of Zope 2.6.3 a security hole was closed - no security check was happening when 'context' and 'container' were bound to a script. Adding the check broke lots of sites where existing scripts had the container binding but the users of the scripts didn't have access to the container (e.g. workflow scripts). This meant getting unauthorized even if the container binding wasn't used in the script. Now, instead of raising unauthorized at binding time, we bind to an UnauthorizedBinding that will allow the script to run if it doesn't actually use the binding, but will raise a meaningful unauthorized error if the binding is accessed. This makes the backward compatibility problem less painful because only those actually using the container binding (for ex. workflow scripts) need to take explicit action to fix existing sites.""" def __init__(self, name, wrapped): self._name = name self._wrapped = wrapped __allow_access_to_unprotected_subobjects__ = 1 __roles__ = _what_not_even_god_should_do def __repr__(self): return '' % self._name def __getattr__(self, name, default=None): # Make *extra* sure that the wrapper isn't used to access # __call__, etc. if name.startswith('__'): self.__you_lose() return guarded_getattr(self._wrapped, name, default) #return getattr(self._wrapped, name, default) def __you_lose(self): name = self.__dict__['_name'] raise Unauthorized('Not authorized to access binding: %s' % name) __str__ = __call__ = index_html = __you_lose class Bindings: security = ClassSecurityInfo() _Bindings_client = None security.declareProtected('Change bindings', 'ZBindings_edit') def ZBindings_edit(self, mapping): names = self._setupBindings(mapping) self._prepareBindCode() self._editedBindings() security.declareProtected('Change bindings', 'ZBindings_setClient') def ZBindings_setClient(self, clientname): '''Name the binding to be used as the "client". This is used by classes such as DTMLFile that want to choose an object on which to operate by default.''' self._Bindings_client = str(clientname) def _editedBindings(self): # Override to receive notification when the bindings are edited. pass def _setupBindings(self, names={}): self._bind_names = names = NameAssignments(names) return names security.declareProtected(view_management_screens, 'getBindingAssignments') def getBindingAssignments(self): if not hasattr(self, '_bind_names'): self._setupBindings() return self._bind_names def __before_publishing_traverse__(self, self2, request): path = request['TraversalRequestNameStack'] names = self.getBindingAssignments() if (not names.isNameAssigned('name_subpath') or (path and hasattr(self.aq_base, path[-1])) ): return subpath = path[:] path[:] = [] subpath.reverse() request.set('traverse_subpath', subpath) def _createBindCode(self, names): return names._createCodeBlockForMapping() def _prepareBindCode(self): # Creates: # - a code block that quickly generates "bound_data" and # modifies the "kw" variable. # - a count of the bound arguments. # Saves them in _v_bindcode and _v_bindcount. # Returns . names = self.getBindingAssignments() if names.isAnyNameAssigned(): bindcode, bindcount = self._createBindCode(names) else: bindcode, bindcount = None, 0 self._v_bindcode = bindcode self._v_bindcount = bindcount return bindcode def _getBindCount(self): bindcount = getattr(self, '_v_bindcount', _marker) if bindcount is _marker: self._prepareBindCode() bindcount = self._v_bindcount return bindcount def _getContext(self): # Utility for bindcode. while 1: self = self.aq_parent if not getattr(self, '_is_wrapperish', None): parent = getattr(self, 'aq_parent', None) inner = getattr(self, 'aq_inner', None) container = getattr(inner, 'aq_parent', None) try: getSecurityManager().validate(parent, container, '', self) except Unauthorized: return UnauthorizedBinding('context', self) return self def _getContainer(self): # Utility for bindcode. while 1: self = self.aq_inner.aq_parent if not getattr(self, '_is_wrapperish', None): parent = getattr(self, 'aq_parent', None) inner = getattr(self, 'aq_inner', None) container = getattr(inner, 'aq_parent', None) try: getSecurityManager().validate(parent, container, '', self) except Unauthorized: return UnauthorizedBinding('container', self) return self def _getTraverseSubpath(self): # Utility for bindcode. if hasattr(self, 'REQUEST'): return self.REQUEST.other.get('traverse_subpath', []) else: return [] def _getNamespace(self, caller_namespace, kw): # Utility for bindcode. if caller_namespace is None: # Try to get the caller's namespace by scanning # the keyword arguments for an argument with the # same name as the assigned name for name_ns. names = self.getBindingAssignments() assigned_name = names.getAssignedName('name_ns') caller_namespace = kw.get(assigned_name, None) if caller_namespace is None: # Create an empty namespace. return self._Bindings_ns_class() return caller_namespace def __call__(self, *args, **kw): '''Calls the script.''' return self._bindAndExec(args, kw, None) def __render_with_namespace__(self, namespace): '''Calls the script with the specified namespace.''' namevals = {} # Try to find unbound parameters in the namespace, if the # namespace is bound. if self.getBindingAssignments().isNameAssigned('name_ns'): code = self.func_code for name in code.co_varnames[:code.co_argcount]: try: namevals[name] = namespace[name] except KeyError: pass return self._bindAndExec((), namevals, namespace) render = __call__ def _bindAndExec(self, args, kw, caller_namespace): '''Prepares the bound information and calls _exec(), possibly with a namespace. ''' bindcode = getattr(self, '_v_bindcode', _marker) if bindcode is _marker: bindcode = self._prepareBindCode() # Execute the script in a new security context (including the # bindings preparation). security = getSecurityManager() security.addContext(self) try: if bindcode is None: bound_data = {} else: bound_data = [] exec bindcode bound_data = bound_data[0] return self._exec(bound_data, args, kw) finally: security.removeContext(self) InitializeClass(Bindings)