AccessControl 7.6__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- AccessControl/AuthEncoding.py +48 -0
- AccessControl/ImplC.py +39 -0
- AccessControl/ImplPython.py +914 -0
- AccessControl/Implementation.py +111 -0
- AccessControl/Owned.py +30 -0
- AccessControl/Permission.py +179 -0
- AccessControl/PermissionMapping.py +165 -0
- AccessControl/PermissionRole.py +19 -0
- AccessControl/Permissions.py +65 -0
- AccessControl/Role.py +34 -0
- AccessControl/SecurityInfo.py +502 -0
- AccessControl/SecurityManagement.py +84 -0
- AccessControl/SecurityManager.py +30 -0
- AccessControl/SimpleObjectPolicies.py +151 -0
- AccessControl/SpecialUsers.py +25 -0
- AccessControl/User.py +54 -0
- AccessControl/ZopeGuards.py +712 -0
- AccessControl/ZopeSecurityPolicy.py +61 -0
- AccessControl/__init__.py +55 -0
- AccessControl/_compat.h +18 -0
- AccessControl/cAccessControl.c +2526 -0
- AccessControl/cAccessControl.cp315-win_amd64.pyd +0 -0
- AccessControl/class_init.py +94 -0
- AccessControl/configure.zcml +5 -0
- AccessControl/frozen.py +57 -0
- AccessControl/interfaces.py +390 -0
- AccessControl/logger_wrapper.py +8 -0
- AccessControl/meta.zcml +52 -0
- AccessControl/metaconfigure.py +61 -0
- AccessControl/owner.py +288 -0
- AccessControl/permissions.zcml +100 -0
- AccessControl/requestmethod.py +96 -0
- AccessControl/rolemanager.py +490 -0
- AccessControl/safe_formatter.py +86 -0
- AccessControl/security.py +203 -0
- AccessControl/tainted.py +195 -0
- AccessControl/tests/__init__.py +0 -0
- AccessControl/tests/actual_python.py +260 -0
- AccessControl/tests/mixed_module/__init__.py +9 -0
- AccessControl/tests/mixed_module/submodule/__init__.py +5 -0
- AccessControl/tests/private_module/__init__.py +5 -0
- AccessControl/tests/private_module/submodule/__init__.py +5 -0
- AccessControl/tests/public_module/__init__.py +11 -0
- AccessControl/tests/public_module/submodule/__init__.py +5 -0
- AccessControl/tests/testClassSecurityInfo.py +192 -0
- AccessControl/tests/testImplementation.py +65 -0
- AccessControl/tests/testModuleSecurity.py +335 -0
- AccessControl/tests/testPermissionMapping.py +12 -0
- AccessControl/tests/testPermissionRole.py +170 -0
- AccessControl/tests/testSecurityManager.py +264 -0
- AccessControl/tests/testZCML.py +489 -0
- AccessControl/tests/testZopeGuards.py +1086 -0
- AccessControl/tests/testZopeSecurityPolicy.py +799 -0
- AccessControl/tests/test_frozen.py +124 -0
- AccessControl/tests/test_owner.py +289 -0
- AccessControl/tests/test_requestmethod.py +138 -0
- AccessControl/tests/test_rolemanager.py +137 -0
- AccessControl/tests/test_safe_formatter.py +226 -0
- AccessControl/tests/test_safeiter.py +59 -0
- AccessControl/tests/test_tainted.py +320 -0
- AccessControl/tests/test_userfolder.py +198 -0
- AccessControl/tests/test_users.py +666 -0
- AccessControl/unauthorized.py +38 -0
- AccessControl/userfolder.py +432 -0
- AccessControl/users.py +502 -0
- accesscontrol-7.6.dist-info/METADATA +474 -0
- accesscontrol-7.6.dist-info/RECORD +71 -0
- accesscontrol-7.6.dist-info/WHEEL +5 -0
- accesscontrol-7.6.dist-info/entry_points.txt +2 -0
- accesscontrol-7.6.dist-info/licenses/LICENSE.txt +44 -0
- accesscontrol-7.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,502 @@
|
|
|
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
|
+
"""SecurityInfo objects and access control constants.
|
|
14
|
+
|
|
15
|
+
SecurityInfo objects are used in class definitions to allow
|
|
16
|
+
a declarative style of associating access control information
|
|
17
|
+
with class attributes.
|
|
18
|
+
|
|
19
|
+
More information on using SecurityInfo and guide to Zope security
|
|
20
|
+
for developers can be found in the dev.zope.org "Declarative Security"
|
|
21
|
+
project:
|
|
22
|
+
|
|
23
|
+
http://dev.zope.org/Wikis/DevSite/Projects/DeclarativeSecurity
|
|
24
|
+
|
|
25
|
+
While SecurityInfo objects largely remove the need for Python
|
|
26
|
+
programmers to care about the underlying implementation, there
|
|
27
|
+
are several constants defined that should be used by code that
|
|
28
|
+
must set __roles__ attributes directly. (the constants are also
|
|
29
|
+
accessible from the AccessControl namespace). The defined access
|
|
30
|
+
control constants and their meanings are:
|
|
31
|
+
|
|
32
|
+
ACCESS_PUBLIC: accessible from restricted code and possibly
|
|
33
|
+
through the web (if object has a docstring)
|
|
34
|
+
|
|
35
|
+
ACCESS_PRIVATE: accessible only from python code
|
|
36
|
+
|
|
37
|
+
ACCESS_NONE: no access
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import sys
|
|
42
|
+
from logging import getLogger
|
|
43
|
+
|
|
44
|
+
from Acquisition import Implicit
|
|
45
|
+
from Persistence import Persistent
|
|
46
|
+
|
|
47
|
+
from AccessControl import frozen
|
|
48
|
+
from AccessControl.class_init import InitializeClass
|
|
49
|
+
from AccessControl.ImplPython import _what_not_even_god_should_do
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# always patch Persistent before ClassSecurityInfo is used
|
|
53
|
+
Persistent.__class_init__ = InitializeClass
|
|
54
|
+
|
|
55
|
+
LOG = getLogger('SecurityInfo')
|
|
56
|
+
|
|
57
|
+
# Security constants - these are imported into the AccessControl
|
|
58
|
+
# namespace and can be referenced as AccessControl.PUBLIC etc.
|
|
59
|
+
|
|
60
|
+
ACCESS_NONE = _what_not_even_god_should_do
|
|
61
|
+
ACCESS_PRIVATE = ()
|
|
62
|
+
ACCESS_PUBLIC = None
|
|
63
|
+
|
|
64
|
+
_marker = []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SecurityInfo(Implicit):
|
|
68
|
+
"""Encapsulate security information."""
|
|
69
|
+
|
|
70
|
+
__security_info__ = 1
|
|
71
|
+
|
|
72
|
+
__roles__ = ACCESS_PRIVATE
|
|
73
|
+
|
|
74
|
+
def __init__(self):
|
|
75
|
+
self.names = {}
|
|
76
|
+
self.roles = {}
|
|
77
|
+
self._unused_protected_decorators = set()
|
|
78
|
+
|
|
79
|
+
def _setaccess(self, names, access):
|
|
80
|
+
for name in names:
|
|
81
|
+
if self.names.get(name, access) != access:
|
|
82
|
+
LOG.warning(
|
|
83
|
+
f'Conflicting security declarations for {name!r}')
|
|
84
|
+
self._warnings = 1
|
|
85
|
+
self.names[name] = access
|
|
86
|
+
|
|
87
|
+
declarePublic__roles__ = ACCESS_PRIVATE
|
|
88
|
+
|
|
89
|
+
def declarePublic(self, name, *names):
|
|
90
|
+
"""Declare names to be publicly accessible."""
|
|
91
|
+
self._setaccess((name,) + names, ACCESS_PUBLIC)
|
|
92
|
+
|
|
93
|
+
declarePrivate__roles__ = ACCESS_PRIVATE
|
|
94
|
+
|
|
95
|
+
def declarePrivate(self, name, *names):
|
|
96
|
+
"""Declare names to be inaccessible to restricted code."""
|
|
97
|
+
self._setaccess((name,) + names, ACCESS_PRIVATE)
|
|
98
|
+
|
|
99
|
+
declareProtected__roles__ = ACCESS_PRIVATE
|
|
100
|
+
|
|
101
|
+
def declareProtected(self, permission_name, name, *names):
|
|
102
|
+
"""Declare names to be associated with a permission."""
|
|
103
|
+
self._setaccess((name,) + names, permission_name)
|
|
104
|
+
|
|
105
|
+
declareObjectPublic__roles__ = ACCESS_PRIVATE
|
|
106
|
+
|
|
107
|
+
def declareObjectPublic(self):
|
|
108
|
+
"""Declare the object to be publicly accessible."""
|
|
109
|
+
self._setaccess(('',), ACCESS_PUBLIC)
|
|
110
|
+
|
|
111
|
+
declareObjectPrivate__roles__ = ACCESS_PRIVATE
|
|
112
|
+
|
|
113
|
+
def declareObjectPrivate(self):
|
|
114
|
+
"""Declare the object to be inaccessible to restricted code."""
|
|
115
|
+
self._setaccess(('',), ACCESS_PRIVATE)
|
|
116
|
+
|
|
117
|
+
declareObjectProtected__roles__ = ACCESS_PRIVATE
|
|
118
|
+
|
|
119
|
+
def declareObjectProtected(self, permission_name):
|
|
120
|
+
"""Declare the object to be associated with a permission."""
|
|
121
|
+
self._setaccess(('',), permission_name)
|
|
122
|
+
|
|
123
|
+
public__roles__ = ACCESS_PRIVATE
|
|
124
|
+
|
|
125
|
+
def public(self, func):
|
|
126
|
+
"""Decorate a function to be publicly accessible."""
|
|
127
|
+
self.declarePublic(func.__name__) # NOQA: D001
|
|
128
|
+
return func
|
|
129
|
+
|
|
130
|
+
private__roles__ = ACCESS_PRIVATE
|
|
131
|
+
|
|
132
|
+
def private(self, func):
|
|
133
|
+
"""Decorate a function to be inaccessible to restricted code."""
|
|
134
|
+
self.declarePrivate(func.__name__) # NOQA: D001
|
|
135
|
+
return func
|
|
136
|
+
|
|
137
|
+
protected__roles__ = ACCESS_PRIVATE
|
|
138
|
+
|
|
139
|
+
def protected(self, permission_name):
|
|
140
|
+
"""Return a decorator to associate a function with a permission."""
|
|
141
|
+
# the decorator returned is remembered in a set and will
|
|
142
|
+
# remove itself upon call. self.apply will check for an empty
|
|
143
|
+
# set and raise an AssertionError otherwise.
|
|
144
|
+
key = f"'{permission_name}':{id(lambda x: x)}"
|
|
145
|
+
|
|
146
|
+
def decor(func):
|
|
147
|
+
self.declareProtected(permission_name, func.__name__) # NOQA: D001
|
|
148
|
+
self._unused_protected_decorators.remove(key)
|
|
149
|
+
return func
|
|
150
|
+
# make sure our key algo creates unique-enough keys
|
|
151
|
+
if key in self._unused_protected_decorators:
|
|
152
|
+
raise KeyError(f"Duplicate key: {key}")
|
|
153
|
+
self._unused_protected_decorators.add(key)
|
|
154
|
+
return decor
|
|
155
|
+
|
|
156
|
+
setPermissionDefault__roles__ = ACCESS_PRIVATE
|
|
157
|
+
|
|
158
|
+
def setPermissionDefault(self, permission_name, roles):
|
|
159
|
+
"""Declare default roles for a permission"""
|
|
160
|
+
rdict = {}
|
|
161
|
+
for role in roles:
|
|
162
|
+
rdict[role] = 1
|
|
163
|
+
if self.roles.get(permission_name, rdict) != rdict:
|
|
164
|
+
LOG.warning('Conflicting default role'
|
|
165
|
+
' declarations for permission "%s"' % permission_name)
|
|
166
|
+
self._warnings = 1
|
|
167
|
+
self.roles[permission_name] = rdict
|
|
168
|
+
|
|
169
|
+
setDefaultAccess__roles__ = ACCESS_PRIVATE
|
|
170
|
+
|
|
171
|
+
def setDefaultAccess(self, access):
|
|
172
|
+
"""Declare default attribute access policy.
|
|
173
|
+
|
|
174
|
+
This should be a boolean value, a map of attribute names to
|
|
175
|
+
booleans, or a callable (name, value) -> boolean.
|
|
176
|
+
"""
|
|
177
|
+
if isinstance(access, str):
|
|
178
|
+
access = access.lower()
|
|
179
|
+
if access == 'allow':
|
|
180
|
+
access = 1
|
|
181
|
+
elif access == 'deny':
|
|
182
|
+
access = 0
|
|
183
|
+
else:
|
|
184
|
+
raise ValueError("'allow' or 'deny' expected")
|
|
185
|
+
self.access = access
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ClassSecurityInfo(SecurityInfo):
|
|
189
|
+
"""Encapsulate security information for class objects."""
|
|
190
|
+
|
|
191
|
+
__roles__ = ACCESS_PRIVATE
|
|
192
|
+
|
|
193
|
+
apply__roles__ = ACCESS_PRIVATE
|
|
194
|
+
|
|
195
|
+
def apply(self, classobj):
|
|
196
|
+
"""Apply security information to the given class object."""
|
|
197
|
+
|
|
198
|
+
# make sure all decorators handed out by security.protected were used
|
|
199
|
+
if self._unused_protected_decorators:
|
|
200
|
+
msg = "Class '%r' has %d non-decorator security.protected calls!"
|
|
201
|
+
raise AssertionError(msg % (classobj,
|
|
202
|
+
len(self._unused_protected_decorators)))
|
|
203
|
+
|
|
204
|
+
cdict = classobj.__dict__
|
|
205
|
+
|
|
206
|
+
# Check the class for an existing __ac_permissions__ and
|
|
207
|
+
# incorporate that if present to support older classes or
|
|
208
|
+
# classes that haven't fully switched to using SecurityInfo.
|
|
209
|
+
if '__ac_permissions__' in cdict:
|
|
210
|
+
for item in cdict['__ac_permissions__']:
|
|
211
|
+
permission_name = item[0]
|
|
212
|
+
self._setaccess(item[1], permission_name)
|
|
213
|
+
if len(item) > 2:
|
|
214
|
+
self.setPermissionDefault(permission_name, item[2])
|
|
215
|
+
|
|
216
|
+
# Set __roles__ for attributes declared public or private.
|
|
217
|
+
# Collect protected attribute names in ac_permissions.
|
|
218
|
+
ac_permissions = {}
|
|
219
|
+
for name, access in self.names.items():
|
|
220
|
+
if access in (ACCESS_PRIVATE, ACCESS_PUBLIC, ACCESS_NONE):
|
|
221
|
+
setattr(classobj, '%s__roles__' % name, access)
|
|
222
|
+
else:
|
|
223
|
+
if access not in ac_permissions:
|
|
224
|
+
ac_permissions[access] = []
|
|
225
|
+
ac_permissions[access].append(name)
|
|
226
|
+
|
|
227
|
+
# Now transform our nested dict structure into the nested tuple
|
|
228
|
+
# structure expected of __ac_permissions__ attributes and set
|
|
229
|
+
# it on the class object.
|
|
230
|
+
getRoles = self.roles.get
|
|
231
|
+
__ac_permissions__ = []
|
|
232
|
+
permissions = sorted(ac_permissions.items())
|
|
233
|
+
for permission_name, names in permissions:
|
|
234
|
+
roles = getRoles(permission_name, ())
|
|
235
|
+
if len(roles):
|
|
236
|
+
entry = (permission_name, tuple(names), tuple(roles.keys()))
|
|
237
|
+
else:
|
|
238
|
+
entry = (permission_name, tuple(names))
|
|
239
|
+
__ac_permissions__.append(entry)
|
|
240
|
+
for permission_name, roles in self.roles.items():
|
|
241
|
+
if permission_name not in ac_permissions:
|
|
242
|
+
entry = (permission_name, (), tuple(roles.keys()))
|
|
243
|
+
__ac_permissions__.append(entry)
|
|
244
|
+
setattr(classobj, '__ac_permissions__', tuple(__ac_permissions__))
|
|
245
|
+
|
|
246
|
+
# Take care of default attribute access policy
|
|
247
|
+
access = getattr(self, 'access', _marker)
|
|
248
|
+
if access is not _marker:
|
|
249
|
+
setattr(classobj, '__allow_access_to_unprotected_subobjects__',
|
|
250
|
+
access)
|
|
251
|
+
|
|
252
|
+
if getattr(self, '_warnings', None):
|
|
253
|
+
LOG.warning('Class "%s" had conflicting'
|
|
254
|
+
' security declarations' % classobj.__name__)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class ClassSecurityInformation(ClassSecurityInfo):
|
|
258
|
+
# Default policy is disallow
|
|
259
|
+
access = 0
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
_moduleSecurity = frozen.FreezableDict()
|
|
263
|
+
_appliedModuleSecurity = frozen.FreezableDict()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def secureModule(mname, *imp):
|
|
267
|
+
modsec = _moduleSecurity.get(mname, None)
|
|
268
|
+
if modsec is None:
|
|
269
|
+
if mname in _appliedModuleSecurity:
|
|
270
|
+
return sys.modules[mname]
|
|
271
|
+
return # no MSI, no module
|
|
272
|
+
|
|
273
|
+
if imp:
|
|
274
|
+
__import__(mname, *imp)
|
|
275
|
+
module = sys.modules[mname]
|
|
276
|
+
modsec.apply(module.__dict__)
|
|
277
|
+
_appliedModuleSecurity[mname] = modsec
|
|
278
|
+
try:
|
|
279
|
+
del _moduleSecurity[mname]
|
|
280
|
+
except KeyError:
|
|
281
|
+
# In case of access from multiple threads, the del might fail, but that
|
|
282
|
+
# is OK.
|
|
283
|
+
pass
|
|
284
|
+
return module
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def ModuleSecurityInfo(module_name=None):
|
|
288
|
+
if module_name is not None:
|
|
289
|
+
modsec = _moduleSecurity.get(module_name, None)
|
|
290
|
+
if modsec is not None:
|
|
291
|
+
return modsec
|
|
292
|
+
modsec = _appliedModuleSecurity.get(module_name, None)
|
|
293
|
+
if modsec is not None:
|
|
294
|
+
# Move security info back to to-apply dict (needed for product
|
|
295
|
+
# refresh). Also invoke this check for parent packages already
|
|
296
|
+
# applied
|
|
297
|
+
del _appliedModuleSecurity[module_name]
|
|
298
|
+
_moduleSecurity[module_name] = modsec
|
|
299
|
+
dot = module_name.rfind('.')
|
|
300
|
+
if dot > 0:
|
|
301
|
+
ModuleSecurityInfo(module_name[:dot])
|
|
302
|
+
return modsec
|
|
303
|
+
dot = module_name.rfind('.')
|
|
304
|
+
if dot > 0:
|
|
305
|
+
# If the module is in a package, recursively make sure
|
|
306
|
+
# there are security declarations for the package steps
|
|
307
|
+
# leading to the module
|
|
308
|
+
modname = module_name[dot + 1:]
|
|
309
|
+
pmodsec = ModuleSecurityInfo(module_name[:dot])
|
|
310
|
+
if modname not in pmodsec.names:
|
|
311
|
+
pmodsec.declarePublic(modname) # NOQA: D001
|
|
312
|
+
return _ModuleSecurityInfo(module_name)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class _ModuleSecurityInfo(SecurityInfo):
|
|
316
|
+
"""Encapsulate security information for modules."""
|
|
317
|
+
|
|
318
|
+
__roles__ = ACCESS_PRIVATE
|
|
319
|
+
access = False # deny by default, prevent acquiring 'access' from module
|
|
320
|
+
|
|
321
|
+
def __init__(self, module_name=None):
|
|
322
|
+
self.names = {}
|
|
323
|
+
if module_name is not None:
|
|
324
|
+
global _moduleSecurity # noqa: F824
|
|
325
|
+
_moduleSecurity[module_name] = self
|
|
326
|
+
|
|
327
|
+
__call____roles__ = ACCESS_PRIVATE
|
|
328
|
+
|
|
329
|
+
def __call__(self, name, value):
|
|
330
|
+
"""Callback for __allow_access_to_unprotected_subobjects__ hook."""
|
|
331
|
+
access = self.names.get(name, _marker)
|
|
332
|
+
if access is not _marker:
|
|
333
|
+
return access == ACCESS_PUBLIC
|
|
334
|
+
|
|
335
|
+
return getattr(self, 'access', 0)
|
|
336
|
+
|
|
337
|
+
def _setaccess(self, names, access):
|
|
338
|
+
frozen.check_not_frozen()
|
|
339
|
+
super()._setaccess(names, access)
|
|
340
|
+
|
|
341
|
+
setDefaultAccess__roles__ = ACCESS_PRIVATE
|
|
342
|
+
|
|
343
|
+
def setDefaultAccess(self, access):
|
|
344
|
+
"""Declare default attribute access policy."""
|
|
345
|
+
frozen.check_not_frozen()
|
|
346
|
+
super().setDefaultAccess(access)
|
|
347
|
+
|
|
348
|
+
apply__roles__ = ACCESS_PRIVATE
|
|
349
|
+
|
|
350
|
+
def apply(self, dict):
|
|
351
|
+
"""Apply security information to the given module dict."""
|
|
352
|
+
|
|
353
|
+
# Start with default attribute access policy
|
|
354
|
+
access = getattr(self, 'access', _marker)
|
|
355
|
+
if access is not _marker or len(self.names):
|
|
356
|
+
dict['__allow_access_to_unprotected_subobjects__'] = self
|
|
357
|
+
|
|
358
|
+
if getattr(self, '_warnings', None):
|
|
359
|
+
LOG.warning('Module "%s" had conflicting'
|
|
360
|
+
' security declarations' % dict['__name__'])
|
|
361
|
+
|
|
362
|
+
declareProtected__roles__ = ACCESS_PRIVATE
|
|
363
|
+
|
|
364
|
+
def declareProtected(self, permission_name, *names):
|
|
365
|
+
"""Cannot declare module names protected."""
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
declareObjectProtected__roles__ = ACCESS_PRIVATE
|
|
369
|
+
|
|
370
|
+
def declareObjectProtected(self, permission_name):
|
|
371
|
+
"""Cannot declare module protected."""
|
|
372
|
+
pass
|
|
373
|
+
|
|
374
|
+
setDefaultRoles__roles__ = ACCESS_PRIVATE
|
|
375
|
+
|
|
376
|
+
def setDefaultRoles(self, permission_name, roles):
|
|
377
|
+
"""Cannot set default roles for permissions in a module."""
|
|
378
|
+
pass
|
|
379
|
+
|
|
380
|
+
# Handy little utility functions
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def allow_module(module_name):
|
|
384
|
+
"""Allow a module and all its contents to be used from a
|
|
385
|
+
restricted Script. The argument module_name may be a simple
|
|
386
|
+
or dotted module or package name. Note that if a package
|
|
387
|
+
path is given, all modules in the path will be available."""
|
|
388
|
+
ModuleSecurityInfo(module_name).setDefaultAccess(1)
|
|
389
|
+
dot = module_name.find('.')
|
|
390
|
+
while dot > 0:
|
|
391
|
+
ModuleSecurityInfo(module_name[:dot]).setDefaultAccess(1)
|
|
392
|
+
dot = module_name.find('.', dot + 1)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
allow_module.__roles__ = ACCESS_PRIVATE
|
|
396
|
+
|
|
397
|
+
# Modules that should never be fully accessible from restricted code.
|
|
398
|
+
# A declaration for one of these, or a submodule, is logged by
|
|
399
|
+
# check_dangerous_modules, which freeze_security_declarations calls.
|
|
400
|
+
# PLEASE NOTE: This listing cannot ever be complete and it cannot guard
|
|
401
|
+
# against malicious or sloppy code exposing other unsafe modules! Its
|
|
402
|
+
# purpose is just informational, it is not a safe catch-all.
|
|
403
|
+
DANGEROUS_MODULES = frozenset((
|
|
404
|
+
# processes and code execution
|
|
405
|
+
'_posixsubprocess', '_thread', 'asyncio', 'atexit', 'bdb', 'builtins',
|
|
406
|
+
'code', 'codeop', 'concurrent', 'copyreg', 'cProfile', 'ctypes',
|
|
407
|
+
'_ctypes', 'doctest', 'faulthandler', 'gc', 'importlib', 'inspect',
|
|
408
|
+
'logging', 'marshal', 'multiprocessing', 'nt', 'operator', 'os', 'pdb',
|
|
409
|
+
'pickle', '_pickle', 'pkgutil', 'posix', 'profile', 'pty', 'pydoc',
|
|
410
|
+
'runpy', 'shelve', 'signal', 'site', 'subprocess', 'sys', 'threading',
|
|
411
|
+
'timeit', 'trace', 'traceback', 'types', 'unittest', 'webbrowser',
|
|
412
|
+
'zipimport',
|
|
413
|
+
# filesystem
|
|
414
|
+
'bz2', 'codecs', 'compileall', 'configparser', 'dbm', 'ensurepip',
|
|
415
|
+
'fcntl', 'filecmp', 'fileinput', 'genericpath', 'glob', 'gzip', 'io',
|
|
416
|
+
'_io', 'linecache', 'lzma', 'mailbox', 'mmap', 'netrc', 'ntpath',
|
|
417
|
+
'pathlib', 'posixpath', 'py_compile', 'shutil', 'sqlite3', 'sysconfig',
|
|
418
|
+
'tarfile', 'tempfile', 'venv', 'zipapp', 'zipfile',
|
|
419
|
+
# network
|
|
420
|
+
'ftplib', 'http', 'imaplib', 'poplib', 'select', 'selectors', 'smtplib',
|
|
421
|
+
'socket', 'socketserver', 'ssl', 'urllib', 'wsgiref', 'xmlrpc',
|
|
422
|
+
# information about the system, or process wide state
|
|
423
|
+
'getpass', 'grp', 'locale', 'platform', 'pwd', 'resource',
|
|
424
|
+
# Zope: security declarations and policy, switching users or sites,
|
|
425
|
+
# transactions, the database
|
|
426
|
+
'AccessControl', 'App', 'transaction', 'ZODB', 'Zope2',
|
|
427
|
+
'zope.component.hooks', 'zope.interface',
|
|
428
|
+
# third-party network libraries: requests from the server itself can
|
|
429
|
+
# reach internal services, and httpx can use local Unix sockets
|
|
430
|
+
'aiohttp', 'httpx', 'pycurl', 'requests', 'urllib3',
|
|
431
|
+
))
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def freeze_security_declarations(dangerous_modules=DANGEROUS_MODULES):
|
|
435
|
+
"""Refuse changes to security declarations from now on.
|
|
436
|
+
|
|
437
|
+
Call this once all code has been loaded, for example when Zope has
|
|
438
|
+
started. Afterwards, ModuleSecurityInfo, allow_module, allow_class and
|
|
439
|
+
allow_type raise Forbidden for any change, so new modules, classes and
|
|
440
|
+
types cannot be opened up to restricted code.
|
|
441
|
+
|
|
442
|
+
Normal class security declarations with ClassSecurityInfo still work,
|
|
443
|
+
because classes may be imported later, for example when loading objects
|
|
444
|
+
from the database.
|
|
445
|
+
|
|
446
|
+
Pending declarations are applied first, because importing a module from
|
|
447
|
+
restricted code would otherwise still need to change them. Dangerous
|
|
448
|
+
modules that are fully accessible are logged as a warning, see
|
|
449
|
+
check_dangerous_modules.
|
|
450
|
+
|
|
451
|
+
Calling this when security declarations are already frozen does nothing.
|
|
452
|
+
"""
|
|
453
|
+
if frozen.is_frozen():
|
|
454
|
+
return
|
|
455
|
+
# Most of these modules have likely already been imported, because the
|
|
456
|
+
# declarations are usually made in the module itself or its package.
|
|
457
|
+
# So this is fast.
|
|
458
|
+
for name in sorted(_moduleSecurity):
|
|
459
|
+
try:
|
|
460
|
+
secureModule(name, {}, {})
|
|
461
|
+
except ImportError:
|
|
462
|
+
LOG.info(f'Could not import {name!r} to apply its'
|
|
463
|
+
' module security declarations.')
|
|
464
|
+
|
|
465
|
+
check_dangerous_modules(dangerous_modules)
|
|
466
|
+
frozen.freeze()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def check_dangerous_modules(dangerous_modules=DANGEROUS_MODULES):
|
|
470
|
+
"""Log a warning for dangerous modules accessible from restricted code.
|
|
471
|
+
|
|
472
|
+
Only modules that are fully accessible are logged, for example with
|
|
473
|
+
allow_module, not modules where only specific names are public.
|
|
474
|
+
A submodule of a dangerous module is dangerous as well. Pass
|
|
475
|
+
dangerous_modules to check other modules, for example ``{'my.package'}``.
|
|
476
|
+
"""
|
|
477
|
+
for name in sorted(set(_moduleSecurity) | set(_appliedModuleSecurity)):
|
|
478
|
+
modsec = _appliedModuleSecurity.get(name) or _moduleSecurity[name]
|
|
479
|
+
access = getattr(modsec, 'access', 0)
|
|
480
|
+
if not access or isinstance(access, dict):
|
|
481
|
+
# Only specific names are accessible.
|
|
482
|
+
continue
|
|
483
|
+
parts = name.split('.')
|
|
484
|
+
for index in range(1, len(parts) + 1):
|
|
485
|
+
if '.'.join(parts[:index]) in dangerous_modules:
|
|
486
|
+
LOG.warning(
|
|
487
|
+
f'Module {name!r} is accessible from restricted code.')
|
|
488
|
+
break
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def allow_class(Class):
|
|
492
|
+
"""Allow a class and all of its methods to be used from a
|
|
493
|
+
restricted Script. The argument Class must be a class."""
|
|
494
|
+
frozen.check_not_frozen()
|
|
495
|
+
Class._security = sec = ClassSecurityInfo()
|
|
496
|
+
sec.declareObjectPublic()
|
|
497
|
+
sec.setDefaultAccess(1)
|
|
498
|
+
sec.apply(Class)
|
|
499
|
+
InitializeClass(Class)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
allow_class.__roles__ = ACCESS_PRIVATE
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
"""Security management
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from _thread import get_ident
|
|
17
|
+
|
|
18
|
+
from AccessControl import SpecialUsers
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def getSecurityManager():
|
|
22
|
+
"""Get a security manager, for the current thread.
|
|
23
|
+
"""
|
|
24
|
+
thread_id = get_ident()
|
|
25
|
+
manager = _managers.get(thread_id, None)
|
|
26
|
+
if manager is None:
|
|
27
|
+
nobody = getattr(SpecialUsers, 'nobody', None)
|
|
28
|
+
if nobody is None:
|
|
29
|
+
# Initialize SpecialUsers by importing User.py.
|
|
30
|
+
from AccessControl import User # NOQA: F401
|
|
31
|
+
nobody = SpecialUsers.nobody
|
|
32
|
+
manager = SecurityManager(thread_id, SecurityContext(nobody)) # NOQA
|
|
33
|
+
_managers[thread_id] = manager
|
|
34
|
+
|
|
35
|
+
return manager
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def setSecurityManager(manager):
|
|
39
|
+
"""install *manager* as current security manager for this thread."""
|
|
40
|
+
thread_id = get_ident()
|
|
41
|
+
_managers[thread_id] = manager
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# AccessControl.Implementation inserts SecurityManager.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_managers = {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def newSecurityManager(request, user):
|
|
51
|
+
"""Set up a new security context for a request for a user
|
|
52
|
+
"""
|
|
53
|
+
thread_id = get_ident()
|
|
54
|
+
_managers[thread_id] = SecurityManager( # NOQA: F821
|
|
55
|
+
thread_id,
|
|
56
|
+
SecurityContext(user),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def noSecurityManager():
|
|
61
|
+
try:
|
|
62
|
+
del _managers[get_ident()]
|
|
63
|
+
except BaseException:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def setSecurityPolicy(aSecurityPolicy):
|
|
68
|
+
"""Set the system default security policy.
|
|
69
|
+
|
|
70
|
+
This method should only be caused by system startup code. It should
|
|
71
|
+
never, for example, be called during a web request.
|
|
72
|
+
"""
|
|
73
|
+
SecurityManager.setSecurityPolicy(aSecurityPolicy) # NOQA: F821
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SecurityContext:
|
|
77
|
+
"""The security context is an object used internally to the security
|
|
78
|
+
machinery. It captures data about the current security context.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, user):
|
|
82
|
+
self.stack = []
|
|
83
|
+
self.user = user
|
|
84
|
+
self.objectCache = {}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
'''API module to set the security policy
|
|
14
|
+
'''
|
|
15
|
+
|
|
16
|
+
from AccessControl import ImplPython as _ImplPython
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setSecurityPolicy(aSecurityPolicy):
|
|
20
|
+
"""Set the system default security policy.
|
|
21
|
+
|
|
22
|
+
This method should only be caused by system startup code. It should
|
|
23
|
+
never, for example, be called during a web request.
|
|
24
|
+
"""
|
|
25
|
+
last = _ImplPython._defaultPolicy
|
|
26
|
+
_ImplPython._defaultPolicy = aSecurityPolicy
|
|
27
|
+
return last
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# AccessControl.Implementation inserts SecurityManager.
|