• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Zope / 3956162881

pending completion
3956162881

push

github

Michael Howitz
Update to deprecation warning free releases.

4401 of 7036 branches covered (62.55%)

Branch coverage included in aggregate %.

27161 of 31488 relevant lines covered (86.26%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.5
/src/Shared/DC/Scripts/Bindings.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE
11
#
12
##############################################################################
13

14
import re
1✔
15

16
from AccessControl.class_init import InitializeClass
1✔
17
from AccessControl.PermissionRole import _what_not_even_god_should_do
1✔
18
from AccessControl.Permissions import view_management_screens
1✔
19
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
20
from AccessControl.SecurityManagement import getSecurityManager
1✔
21
from AccessControl.unauthorized import Unauthorized
1✔
22
from AccessControl.ZopeGuards import guarded_getattr
1✔
23
from Acquisition import aq_base
1✔
24
from Acquisition import aq_inner
1✔
25
from Acquisition import aq_parent
1✔
26
from zope.component import queryMultiAdapter as qma
1✔
27

28

29
defaultBindings = {'name_context': 'context',
1✔
30
                   'name_container': 'container',
31
                   'name_m_self': 'script',
32
                   'name_ns': '',
33
                   'name_subpath': 'traverse_subpath',
34
                   }
35

36
_marker = []  # Create a new marker
1✔
37

38

39
class NameAssignments:
1✔
40
    # Note that instances of this class are intended to be immutable
41
    # and persistent but not inherit from ExtensionClass.
42

43
    _exprs = (('name_context', 'self._getContext()'),
1✔
44
              ('name_container', 'self._getContainer()'),
45
              ('name_m_self', 'self'),
46
              ('name_ns', 'self._getNamespace(caller_namespace, kw)'),
47
              ('name_subpath', 'self._getTraverseSubpath()'),
48
              )
49

50
    _isLegalName = re.compile(r'_$|[a-zA-Z][a-zA-Z0-9_]*$').match
1✔
51
    _asgns = {}
1✔
52

53
    __allow_access_to_unprotected_subobjects__ = 1
1✔
54

55
    def __init__(self, mapping=None):
1✔
56
        # mapping is presumably the REQUEST or compatible equivalent.
57
        # Note that we take care not to store expression texts in the ZODB.
58

59
        # The default value is needed for unpickling instances of this class
60
        # which where created before 4.0b2 where this class was still an old
61
        # style class. For details see
62
        # https://github.com/zopefoundation/Zope/issues/205
63
        asgns = {}
1✔
64
        if mapping is None:
1!
65
            mapping = {}
×
66
        _isLegalName = self._isLegalName
1✔
67
        for name, expr in self._exprs:
1✔
68
            if name in mapping:
1✔
69
                assigned_name = mapping[name].strip()
1✔
70
                if not assigned_name:
1!
71
                    continue
×
72
                if not _isLegalName(assigned_name):
1!
73
                    raise ValueError('"%s" is not a valid variable name.' %
×
74
                                     assigned_name)
75
                asgns[name] = assigned_name
1✔
76
        self._asgns = asgns
1✔
77

78
    def isAnyNameAssigned(self):
1✔
79
        if len(self._asgns) > 0:
1✔
80
            return 1
1✔
81
        return 0
1✔
82

83
    def isNameAssigned(self, name):
1✔
84
        return name in self._asgns
1✔
85

86
    def getAssignedName(self, name, default=_marker):
1✔
87
        val = self._asgns.get(name, default)
1✔
88
        if val is _marker:
1!
89
            raise KeyError(name)
×
90
        return val
1✔
91

92
    def getAssignedNames(self):
1✔
93
        # Returns a copy of the assigned names mapping
94
        return self._asgns.copy()
×
95

96
    def getAssignedNamesInOrder(self):
1✔
97
        # Returns the assigned names in the same order as that of
98
        # self._exprs.
99
        rval = []
×
100
        asgns = self._asgns
×
101
        for name, expr in self._exprs:
×
102
            if name in asgns:
×
103
                assigned_name = asgns[name]
×
104
                rval.append(assigned_name)
×
105
        return rval
×
106

107
    def _generateCodeBlock(self, bindtext, assigned_names):
1✔
108
        # Returns a tuple: exec-able code that can compute the value of
109
        # the bindings and eliminate clashing keyword arguments,
110
        # and the number of names bound.
111
        text = ['bound_data.append(%s)\n' % bindtext]
1✔
112
        for assigned_name in assigned_names:
1✔
113
            text.append('if "%s" in kw:\n' % assigned_name)
1✔
114
            text.append('    del kw["%s"]\n' % assigned_name)
1✔
115
        codetext = ''.join(text)
1✔
116
        return (compile(codetext, '<string>', 'exec'), len(assigned_names))
1✔
117

118
    def _createCodeBlockForMapping(self):
1✔
119
        # Generates a code block which generates the "bound_data"
120
        # variable and removes excessive arguments from the "kw"
121
        # variable.  bound_data will be a mapping, for use as a
122
        # global namespace.
123
        exprtext = []
1✔
124
        assigned_names = []
1✔
125
        asgns = self._asgns
1✔
126
        for name, expr in self._exprs:
1✔
127
            if name in asgns:
1✔
128
                assigned_name = asgns[name]
1✔
129
                assigned_names.append(assigned_name)
1✔
130
                exprtext.append(f'"{assigned_name}":{expr},')
1✔
131
        text = '{%s}' % ''.join(exprtext)
1✔
132
        return self._generateCodeBlock(text, assigned_names)
1✔
133

134
    def _createCodeBlockForTuple(self, argNames):
1✔
135
        # Generates a code block which generates the "bound_data"
136
        # variable and removes excessive arguments from the "kw"
137
        # variable.  bound_data will be a tuple, for use as
138
        # positional arguments.
139
        assigned_names = []
×
140
        exprtext = []
×
141
        asgns = self._asgns
×
142
        for argName in argNames:
×
143
            passedLastBoundArg = 1
×
144
            for name, expr in self._exprs:
×
145
                # Provide a value for the available exprs.
146
                if name in asgns:
×
147
                    assigned_name = asgns[name]
×
148
                    if assigned_name == argName:
×
149
                        # The value for this argument will be filled in.
150
                        exprtext.append('%s,' % expr)
×
151
                        assigned_names.append(assigned_name)
×
152
                        passedLastBoundArg = 0
×
153
                        break
×
154
            if passedLastBoundArg:
×
155
                # Found last of bound args.
156
                break
×
157
        text = '(%s)' % ''.join(exprtext)
×
158
        return self._generateCodeBlock(text, assigned_names)
×
159

160

161
class UnauthorizedBinding:
1✔
162
    """Explanation: as of Zope 2.6.3 a security hole was closed - no
163
       security check was happening when 'context' and 'container'
164
       were bound to a script. Adding the check broke lots of sites
165
       where existing scripts had the container binding but the users
166
       of the scripts didn't have access to the container (e.g. workflow
167
       scripts). This meant getting unauthorized even if the container
168
       binding wasn't used in the script.
169

170
       Now, instead of raising unauthorized at binding time, we bind
171
       to an UnauthorizedBinding that will allow the script to run if
172
       it doesn't actually use the binding, but will raise a meaningful
173
       unauthorized error if the binding is accessed. This makes the
174
       backward compatibility problem less painful because only those
175
       actually using the container binding (for ex. workflow scripts)
176
       need to take explicit action to fix existing sites."""
177

178
    def __init__(self, name, wrapped):
1✔
179
        self._name = name
1✔
180
        self._wrapped = wrapped
1✔
181

182
    __allow_access_to_unprotected_subobjects__ = 1
1✔
183
    __roles__ = _what_not_even_god_should_do
1✔
184

185
    def __repr__(self):
1✔
186
        return '<UnauthorizedBinding: %s>' % self._name
×
187

188
    def __getattr__(self, name):
1✔
189
        # Make *extra* sure that the wrapper isn't used to access
190
        # __call__, etc.
191
        if name.startswith('__'):
×
192
            # Acquisition will nowadays try to do an getattr on all
193
            # objects which aren't Acquisition wrappers, asking for a
194
            # __parent__ pointer.  We don't want to raise Unauthorized
195
            # in this case but simply an AttributeError.
196
            if name in ('__parent__', '__name__'):
×
197
                raise AttributeError(name)
×
198

199
            self.__you_lose()
×
200

201
        return guarded_getattr(self._wrapped, name)
×
202

203
    def __you_lose(self):
1✔
204
        name = self.__dict__['_name']
×
205
        raise Unauthorized('Not authorized to access binding: %s' % name)
×
206

207
    __str__ = __call__ = index_html = __you_lose
1✔
208

209

210
class Bindings:
1✔
211

212
    security = ClassSecurityInfo()
1✔
213

214
    _Bindings_client = None
1✔
215

216
    @security.protected('Change bindings')
1✔
217
    def ZBindings_edit(self, mapping):
1✔
218
        self._setupBindings(mapping)
1✔
219
        self._prepareBindCode()
1✔
220
        self._editedBindings()
1✔
221

222
    @security.protected('Change bindings')
1✔
223
    def ZBindings_setClient(self, clientname):
1✔
224
        '''Name the binding to be used as the "client".
225

226
        This is used by classes such as DTMLFile that want to
227
        choose an object on which to operate by default.'''
228
        self._Bindings_client = str(clientname)
×
229

230
    def _editedBindings(self):
1✔
231
        # Override to receive notification when the bindings are edited.
232
        pass
1✔
233

234
    def _setupBindings(self, names={}):
1✔
235
        self._bind_names = names = NameAssignments(names)
1✔
236
        return names
1✔
237

238
    @security.protected(view_management_screens)
1✔
239
    def getBindingAssignments(self):
1✔
240
        if not hasattr(self, '_bind_names'):
1✔
241
            self._setupBindings()
1✔
242
        return self._bind_names
1✔
243

244
    def __before_publishing_traverse__(self, self2, request):
1✔
245
        path = request['TraversalRequestNameStack']
1✔
246
        names = self.getBindingAssignments()
1✔
247
        if not names.isNameAssigned('name_subpath') or \
1✔
248
           (path and hasattr(aq_base(self), path[-1])) or \
249
           (path and qma((self, request), name=path[-1]) is not None):
250
            return
1✔
251
        subpath = path[:]
1✔
252
        path[:] = []
1✔
253
        subpath.reverse()
1✔
254
        request.set('traverse_subpath', subpath)
1✔
255

256
    def _createBindCode(self, names):
1✔
257
        return names._createCodeBlockForMapping()
1✔
258

259
    def _prepareBindCode(self):
1✔
260
        # Creates:
261
        # - a code block that quickly generates "bound_data" and
262
        #   modifies the "kw" variable.
263
        # - a count of the bound arguments.
264
        # Saves them in _v_bindcode and _v_bindcount.
265
        # Returns .
266
        names = self.getBindingAssignments()
1✔
267
        if names.isAnyNameAssigned():
1✔
268
            bindcode, bindcount = self._createBindCode(names)
1✔
269
        else:
270
            bindcode, bindcount = None, 0
1✔
271
        self._v_bindcode = bindcode
1✔
272
        self._v_bindcount = bindcount
1✔
273
        return bindcode
1✔
274

275
    def _getBindCount(self):
1✔
276
        bindcount = getattr(self, '_v_bindcount', _marker)
×
277
        if bindcount is _marker:
×
278
            self._prepareBindCode()
×
279
            bindcount = self._v_bindcount
×
280
        return bindcount
×
281

282
    def _getContext(self):
1✔
283
        # Utility for bindcode.
284
        while 1:
285
            self = aq_parent(self)
1✔
286
            if not getattr(self, '_is_wrapperish', None):
1!
287
                parent = aq_parent(self)
1✔
288
                inner = aq_inner(self)
1✔
289
                container = aq_parent(inner)
1✔
290
                try:
1✔
291
                    getSecurityManager().validate(parent, container, '', self)
1✔
292
                except Unauthorized:
1✔
293
                    return UnauthorizedBinding('context', self)
1✔
294
                return self
1✔
295

296
    def _getContainer(self):
1✔
297
        # Utility for bindcode.
298
        while 1:
299
            self = aq_parent(aq_inner(self))
1✔
300
            if not getattr(self, '_is_wrapperish', None):
1!
301
                parent = aq_parent(self)
1✔
302
                inner = aq_inner(self)
1✔
303
                container = aq_parent(inner)
1✔
304
                try:
1✔
305
                    getSecurityManager().validate(parent, container, '', self)
1✔
306
                except Unauthorized:
1✔
307
                    return UnauthorizedBinding('container', self)
1✔
308
                return self
1✔
309

310
    def _getTraverseSubpath(self):
1✔
311
        # Utility for bindcode.
312
        if hasattr(self, 'REQUEST'):
1✔
313
            return self.REQUEST.other.get('traverse_subpath', [])
1✔
314
        else:
315
            return []
1✔
316

317
    def _getNamespace(self, caller_namespace, kw):
1✔
318
        # Utility for bindcode.
319
        if caller_namespace is None:
1✔
320
            # Try to get the caller's namespace by scanning
321
            # the keyword arguments for an argument with the
322
            # same name as the assigned name for name_ns.
323
            names = self.getBindingAssignments()
1✔
324
            assigned_name = names.getAssignedName('name_ns')
1✔
325
            caller_namespace = kw.get(assigned_name, None)
1✔
326
        if caller_namespace is None:
1✔
327
            # Create an empty namespace.
328
            return self._Bindings_ns_class()
1✔
329
        return caller_namespace
1✔
330

331
    def __call__(self, *args, **kw):
1✔
332
        '''Calls the script.'''
333
        return self._bindAndExec(args, kw, None)
1✔
334

335
    def __render_with_namespace__(self, namespace):
1✔
336
        '''Calls the script with the specified namespace.'''
337
        namevals = {}
1✔
338
        # Try to find unbound parameters in the namespace, if the
339
        # namespace is bound.
340
        if self.getBindingAssignments().isNameAssigned('name_ns'):
1!
341
            code = self.__code__
1✔
342
            for name in code.co_varnames[:code.co_argcount]:
1!
343
                try:
×
344
                    namevals[name] = namespace[name]
×
345
                except KeyError:
×
346
                    pass
×
347
        return self._bindAndExec((), namevals, namespace)
1✔
348

349
    render = __call__
1✔
350

351
    def _bindAndExec(self, args, kw, caller_namespace):
1✔
352
        '''Prepares the bound information and calls _exec(), possibly
353
        with a namespace.
354
        '''
355
        bindcode = getattr(self, '_v_bindcode', _marker)
1✔
356
        if bindcode is _marker:
1✔
357
            bindcode = self._prepareBindCode()
1✔
358

359
        # Execute the script in a new security context (including the
360
        # bindings preparation).
361
        security = getSecurityManager()
1✔
362
        security.addContext(self)
1✔
363
        try:
1✔
364
            if bindcode is None:
1✔
365
                bound_data = {}
1✔
366
            else:
367
                bound_data = []
1✔
368
                exec(bindcode)
1✔
369
                bound_data = bound_data[0]
1✔
370
            return self._exec(bound_data, args, kw)
1✔
371
        finally:
372
            security.removeContext(self)
1✔
373

374

375
InitializeClass(Bindings)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc