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

zopefoundation / Zope / 6263629025

21 Sep 2023 03:12PM UTC coverage: 82.146% (-0.01%) from 82.159%
6263629025

Pull #1164

github

web-flow
[pre-commit.ci lite] apply automatic fixes
Pull Request #1164: Move all linters to pre-commit.

4353 of 6963 branches covered (0.0%)

Branch coverage included in aggregate %.

487 of 487 new or added lines in 186 files covered. (100.0%)

27394 of 31684 relevant lines covered (86.46%)

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 choose an
227
        object on which to operate by default.
228
        """
229
        self._Bindings_client = str(clientname)
×
230

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

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

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

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

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

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

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

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

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

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

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

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

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

350
    render = __call__
1✔
351

352
    def _bindAndExec(self, args, kw, caller_namespace):
1✔
353
        """Prepares the bound information and calls _exec(), possibly with a
354
        namespace."""
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